Codebase list google-nexus-tools / e51b4f4 bin / main.dart
e51b4f4

Tree @e51b4f4 (Download .tar.gz)

main.dart @e51b4f4raw · history · blame

import 'package:archive/archive_io.dart';
import 'package:http/http.dart' as http;
import 'dart:io' as io;
import 'package:archive/archive.dart';
import 'package:uuid/uuid.dart';
import 'dart:convert';
import 'package:nexustools/sys.dart' as sys;

String macZip =
    'https://dl.google.com/android/repository/platform-tools-latest-darwin.zip';
String linuxZip =
    'https://dl.google.com/android/repository/platform-tools-latest-linux.zip';
String windowsZip =
    'https://dl.google.com/android/repository/platform-tools-latest-windows.zip';
List supportedCPUs = ['amd64', 'x86_64', 'AMD64'];
Map envVars = io.Platform.environment;
double appVersion = 5.4;

// Function for checking for update
Future checkUpdate() async {
  var net = Uri.parse(
      'https://api.github.com/repos/corbindavenport/nexus-tools/releases/latest');
  try {
    var data = await http.read(net);
    var parsedData = json.decode(data);
    // Compare versions
    if (double.parse(parsedData['tag_name']) > appVersion) {
      print(
          '[INFO] Nexus Tools update available! Download: https://git.io/JBuTh');
    } else {
      print('[INFO] You have the latest version of Nexus Tools.');
    }
  } catch (e) {
    print('[EROR] Could not check for updates.');
  }
}

// Function for obtaining Nexus Tools path
// Credit: https://stackoverflow.com/a/25498458
String nexusToolsDir() {
  var home = '';
  if (io.Platform.isMacOS) {
    home = envVars['HOME'];
  } else if (io.Platform.isLinux) {
    home = envVars['HOME'];
  } else if (io.Platform.isWindows) {
    home = envVars['UserProfile'];
  }
  if (home.endsWith('/')) {
    home = home.substring(0, home.length - 1);
  }
  if (io.Platform.isWindows) {
    return '$home\\NexusTools';
  } else {
    return '$home/.nexustools';
  }
}

// Function for installing Platform Tools package
Future installPlatformTools() async {
  var dir = nexusToolsDir();
  // Get the proper ZIP file
  var zip = '';
  if (io.Platform.isMacOS) {
    zip = macZip;
  } else if (io.Platform.isLinux) {
    zip = linuxZip;
  } else if (io.Platform.isWindows) {
    zip = windowsZip;
  }
  // Download file
  print('[....] Downloading Platform Tools package, please wait.');
  var net = Uri.parse(zip);
  try {
    var data = await http.readBytes(net);
    //await zipFile.writeAsBytes(data);
    var archive = ZipDecoder().decodeBytes(data);
    extractArchiveToDisk(archive, dir);
  } catch (e) {
    var error = e.toString();
    print('[EROR] There was an error downloading Platform Tools: $error');
    io.exit(1);
  }
  // Move files out of platform-tools subdirectory and delete the subdirectory
  if (io.Platform.isWindows) {
    await io.Process.run('move', ['$dir\\platform-tools\\*', '$dir'],
        runInShell: true);
    await io.Process.run('rmdir', ['/Q', '/S', '$dir\\platform-tools'],
        runInShell: true);
  } else {
    await io.Process.run(
        '/bin/sh', ['-c', 'mv -f -v $dir/platform-tools/* $dir/']);
    await io.Process.run('/bin/sh', ['-c', 'rm -rf $dir/platform-tools']);
  }
  // Mark binaries in directory as executable
  if (io.Platform.isLinux || io.Platform.isMacOS) {
    await io.Process.run('/bin/sh', ['-c', 'chmod -f +x $dir/*']);
  }
  // Give a progress report
  print('[ OK ] Platform Tools now installed in $dir.');
  // Add binaries to path
  await sys.addPath(dir);
  // Create help link
  if (io.Platform.isWindows) {
    var file = io.File('$dir\\About Nexus Tools.url');
    await file.writeAsString(
        '[InternetShortcut]\nURL=https://github.com/corbindavenport/nexus-tools/blob/master/README.md',
        mode: io.FileMode.writeOnly);
  } else if (io.Platform.isMacOS) {
    var file = io.File('$dir/About Nexus Tools.url');
    await file.writeAsString(
        '[InternetShortcut]\nURL=https://github.com/corbindavenport/nexus-tools/blob/master/README.md',
        mode: io.FileMode.writeOnly);
  } else if (io.Platform.isLinux) {
    var file = io.File('$dir/About Nexus Tools.desktop');
    await file.writeAsString(
        '[Desktop Entry]\nEncoding=UTF-8\nIcon=text-html\nType=Link\nName=About Nexus Tools\nURL=https://github.com/corbindavenport/nexus-tools/blob/master/README.md',
        mode: io.FileMode.writeOnly);
  }
  // Install drivers on Windows
  if (io.Platform.isWindows) {
    // Check if Universal Adb Driver package is already installed
    var info = await io.Process.run('wmic', ['product', 'get', 'Name']);
    var parsedInfo = info.stdout.toString();
    if (parsedInfo.contains('Universal Adb Driver')) {
      print('[ OK ] Universal ADB Drivers already installed.');
    } else {
      // Prompt to install drivers
      print(
          '[WARN] Drivers may be required for ADB if they are not already installed.');
      io.stdout
          .write('[WARN] Install drivers from adb.clockworkmod.com? [Y/N] ');
      var input = io.stdin.readLineSync();
      if (input?.toLowerCase() == 'y') {
        await installWindowsDrivers(dir);
      }
    }
  }
}

// Function for removing Platform Tools package
Future removePlatformTools() async {
  // Nexus Tools 3.2+ (August 2016-Present) installs binaries in ~/.nexustools
  var dir = nexusToolsDir();
  var installExists = false;
  installExists = await io.Directory(dir).exists();
  if (installExists) {
    print(
        '[WARN] Deleting $dir will delete Android System Tools (ADB, Fastboot, etc.).');
    print('[WARN] This will also delete the Nexus Tools application.');
    io.stdout.write('[WARN] Continue with removal? [Y/N] ');
    var input = io.stdin.readLineSync();
    if (input?.toLowerCase() != 'y') {
      return;
    }
    // Proceed with deletion
    await io.Directory(dir).delete(recursive: true);
    print('[ OK ] Deleted directory at $dir.');
    print('[INFO] Nexus Tools can be re-installed at https://git.io/JBuTh.');
  } else {
    print('[EROR] No installation found at $dir.');
  }
}

// Function for installing Windows Universal ADB drivers
// Drivers provided by ClockWorkMod: https://adb.clockworkmod.com/
Future installWindowsDrivers(String dir) async {
  print('[....] Downloading drivers, please wait.');
  var net = Uri.parse(
      'https://github.com/koush/adb.clockworkmod.com/releases/latest/download/UniversalAdbDriverSetup.msi');
  try {
    var data = await http.readBytes(net);
    var file = io.File('$dir\\ADB Drivers.msi');
    await file.writeAsBytes(data, mode: io.FileMode.writeOnly);
    print('[....] Opening driver installer.');
    await io.Process.run(
        'start', ['/wait', 'msiexec.exe', '/i', '$dir\\ADB Drivers.msi'],
        runInShell: true);
  } catch (e) {
    print(
        '[EROR] There was an error downloading drivers, try downloading them from adb.clockworkmod.com.');
  }
}

// Function for Plausible Analytics reporting
void connectAnalytics() async {
  var uuid = Uuid();
  var id = uuid.v4();
  // Get exact operating system
  var realOS = '';
  var isWSL = await io.Directory('/mnt/c/Windows').exists();
  var isChromeOS = await io.Directory('/usr/share/themes/CrosAdapta').exists();
  if (isWSL) {
    realOS = 'wsl';
  } else if (isChromeOS) {
    realOS = 'chromeos';
  } else {
    realOS = io.Platform.operatingSystem;
  }
  var cpu = await sys.getCPUArchitecture();
  // Set data
  var net = Uri.parse('https://plausible.io/api/event');
  var netHeaders = {
    'user-agent': 'Nexus Tools',
    'X-Forwarded-For': '127.0.0.1',
    'Content-Type': 'application/json',
    'User-Agent': 'Mozilla/5.0 ($realOS) AppleWebKit/500 (KHTML, like Gecko) Chrome/$appVersion $id'
  };
  var netBody = '{"name":"pageview","url":"app://localhost/$realOS/$cpu","domain":"nexustools.corbin.io"}';
  // Send request
  try {
    await http.post(net, headers: netHeaders, body: netBody);
  } catch (_) {
    // Do nothing
  }
}

// Pre-installation steps
Future checkInstall() async {
  // Check if directory already exists
  var dir = nexusToolsDir();
  var installExists = false;
  if (io.Platform.isWindows) {
    installExists = await io.File('$dir\\adb.exe').exists();
  } else {
    installExists = await io.File('$dir/adb').exists();
  }
  if (installExists) {
    io.stdout.write(
        '[WARN] Platform tools already installed in $dir. Continue? [Y/N] ');
    var input = io.stdin.readLineSync();
    if (input?.toLowerCase() != 'y') {
      io.exit(0);
    }
  } else {
    // Make the directory
    await io.Directory(dir).create(recursive: true);
    print('[ OK ] Created folder at $dir.');
  }
  // Check if ADB is already installed
  sys.checkIfInstalled(dir, 'adb', 'ADB');
  // Check if Fastboot is already installed
  sys.checkIfInstalled(dir, 'fastboot', 'Fastboot');
  // Check CPU architecture
  var cpu = await sys.getCPUArchitecture();
  if (supportedCPUs.contains(cpu)) {
    print('[ OK ] Your hardware platform is supported, yay!');
  } else if (io.Platform.isMacOS && (cpu == 'arm64')) {
    print(
        '[WARN] Google does not provide native Apple Silicon binaries yet, x86_64 binaries will be installed');
  } else {
    print(
        '[EROR] Your hardware platform is detected as $cpu, but Google only provides Platform Tools for x86-based platforms.');
    io.exit(1);
  }
  // Display environment-specific warnings
  var isWSL = await io.Directory('/mnt/c/Windows').exists();
  var isChromeOS = await io.Directory('/usr/share/themes/CrosAdapta').exists();
  if (isWSL) {
    print(
        '[WARN] WSL does not support USB devices, you will only be able to use ADB over Wi-Fi.');
  } else if (isChromeOS) {
    print('[WARN] Chrome OS 75 or higher is required for USB support.');
  }
}

void printHelp() {
  var helpDoc = '''
Nexus Tools $appVersion
Downloader/management app for Android SDK Platform Tools

Usage: nexustools [OPTIONS]

Example: nexustools -i (this installs Platform Tools)

 -i, --install                 Install/update Android Platform Tools
 -r, --remove                  Remove Platform Tools
 -n, --no-analytics            Run Nexus Tools without Plausible Analytics
                               (analytics is only run on install)
 -c, --check                   Check for Nexus Tools update
 -h, --help                    Display this help message
  ''';
  print(helpDoc);
}

void main(List<String> arguments) async {
  if (arguments.contains('-i') || arguments.contains('--install')) {
    print('[INFO] Nexus Tools $appVersion');
    // Check version unless Nexus Tools is running from web (curl) installer
    // The web installer adds the -w parameter
    if (!arguments.contains('-w')) {
      await checkUpdate();
    }
    // Start analytics unless opted out
    if (arguments.contains('--no-analytics')) {
      print('[ OK ] Plausible Analytics are disabled.');
    } else {
      connectAnalytics();
    }
    // Start installation
    await checkInstall();
    await installPlatformTools();
    // Post-install
    var appName = '';
    if (io.Platform.isWindows) {
      appName = 'Command Line window (not a new tab!)';
    } else {
      appName = 'Terminal window';
    }
    print(
        '[INFO] Installation complete! Open a new $appName to apply changes.');
    print('[INFO] Run "nexustools --help" at any time for more options.');
    print(
        '[INFO] Join the Discord server: https://discord.com/invite/59wfy5cNHw');
    print('[INFO] Donate to support development: https://git.io/J4jct');
  } else if (arguments.contains('-r') || arguments.contains('--remove')) {
    print('[INFO] Nexus Tools $appVersion');
    // Start removal
    await removePlatformTools();
  } else if (arguments.contains('-h') || arguments.contains('--help')) {
    printHelp();
  } else if (arguments.contains('-c') || arguments.contains('--check')) {
    await checkUpdate();
  } else {
    print('[EROR] Invalid arguments. Run nexustools -h for help!');
  }
}