diff options
Diffstat (limited to 'build/install-gaia.py')
-rw-r--r-- | build/install-gaia.py | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/build/install-gaia.py b/build/install-gaia.py new file mode 100644 index 0000000..9ff2ae0 --- /dev/null +++ b/build/install-gaia.py @@ -0,0 +1,176 @@ +"""Usage: python %prog [ADB_PATH] [REMOTE_PATH] + +ADB_PATH is the path to the |adb| executable we should run. +REMOTE_PATH is the path to push the gaia webapps directory to. + +Used by |make install-gaia| to push files to a device. You shouldn't run +this file directly. + +""" + +import sys +import os +import hashlib +import subprocess +from tempfile import mkstemp + +def compute_local_hash(filename, hashes): + h = hashlib.sha1() + with open(filename,'rb') as f: + for chunk in iter(lambda: f.read(256 * h.block_size), b''): + h.update(chunk) + hashes[filename] = h.hexdigest() + +def compute_local_hashes_in_dir(dir, hashes): + def visit(arg, dirname, names): + for filename in [os.path.join(dirname, name) for name in names]: + if not os.path.isfile(filename): + continue + compute_local_hash(filename, hashes) + + os.path.walk(dir, visit, None) + +def compute_local_hashes(): + hashes = {} + compute_local_hashes_in_dir('webapps', hashes) + compute_local_hash('user.js', hashes) + return hashes + +def adb_push(local, remote): + global adb_cmd + subprocess.check_call([adb_cmd, 'push', local, remote]) + +def adb_shell(cmd, ignore_error=False): + global adb_cmd + + # Output the return code so we can check whether the command executed + # successfully. + new_cmd = cmd + '; echo "RETURN CODE: $?"' + + # universal_newlines=True because adb shell returns CRLF separators. + proc = subprocess.Popen([adb_cmd, 'shell', new_cmd], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + (stdout, stderr) = proc.communicate() + if stderr.strip(): + raise Exception('adb shell "%s" returned the following unexpected error: "%s"' % + (cmd, stderr.strip())) + if proc.returncode != 0: + raise Exception('adb shell "%s" exited with error %d' % (cmd, proc.returncode)) + + split = [line for line in stdout.split('\n') if line.strip()] + if not ignore_error and not split[-1].startswith('RETURN CODE: 0'): + raise Exception('adb shell "%s" did not complete successfully. Output:\n%s' % (cmd, stdout)) + + # Don't return the "RETURN CODE: 0" line! + return split[0:-1] + + +def compute_remote_hashes(): + hashes = {} + adb_out = adb_shell('cd /data/local && find . -type f | xargs sha1sum') + for (hash, filename) in [line.split() for line in adb_out]: + # Strip off './' from the filename. + if filename.startswith('./'): + filename = filename[2:] + else: + raise Exception('Unexpected filename %s' % filename) + + hashes[filename] = hash + return hashes + +INDEXED_DB_FOLDER = 'indexedDB/' + +def remove_from_remote(local_hashes, remote_hashes): + """Remove any files from the remote device which don't appear in + local_hashes. + + """ + + # Keep indexedDB content + to_keep = set() + for path in remote_hashes: + if path[:len(INDEXED_DB_FOLDER)] == INDEXED_DB_FOLDER: + to_keep.add(path) + + to_remove = list(set(remote_hashes.keys()) - set(local_hashes.keys()) - to_keep) + + if not to_remove: + return + + print 'Removing from device:\n%s\n' % '\n'.join(to_remove) + # Chunk to_remove into 25 files at a time so we don't send too much over + # adb_shell at once. + for files in [to_remove[pos:pos + 25] for pos in xrange(0, len(to_remove), 25)]: + adb_shell('cd /data/local && rm -f %s' % ' '.join(files)) + +def push_to_remote(local_hashes, remote_hashes): + global adb_cmd + + to_push = set() + for (k, v) in local_hashes.items(): + if k not in remote_hashes or remote_hashes[k] != local_hashes[k]: + to_push.add(k) + + if not to_push: + return + + print 'Pushing to device:\n%s' % '\n'.join(list(to_push)) + + tmpfile, tmpfilename = mkstemp() + try: + subprocess.check_call(['tar', '-czf', tmpfilename] + list(to_push)) + adb_push(tmpfilename, '/data/local') + basename = os.path.basename(tmpfilename) + adb_shell('cd /data/local && tar -xzf %s && rm %s' % (basename, basename)) + finally: + os.remove(tmpfilename) + +def install_gaia_fast(): + os.chdir('profile') + try: + local_hashes = compute_local_hashes() + remote_hashes = compute_remote_hashes() + remove_from_remote(local_hashes, remote_hashes) + push_to_remote(local_hashes, remote_hashes) + finally: + os.chdir('..') + +def install_gaia_slow(): + global adb_cmd, remote_path + webapps_path = remote_path + '/webapps' + adb_shell("rm -r " + webapps_path, ignore_error=True) + adb_shell("rm /data/local/user.js", ignore_error=True) + adb_push('profile/webapps', webapps_path) + adb_push('profile/user.js', '/data/local') + +def install_gaia(): + global remote_path + try: + if remote_path == "/system/b2g": + # XXX Force slow method until we fix the fast one to support + # files in both /system/b2g and /data/local + # install_gaia_fast() + install_gaia_slow() + else: + install_gaia_fast() + except: + # If anything goes wrong, fall back to the slow method. + install_gaia_slow() + +if __name__ == '__main__': + if len(sys.argv) > 3: + print >>sys.stderr, 'Too many arguments!\n' + print >>sys.stderr, \ + 'Usage: python %s [ADB_PATH] [REMOTE_PATH]\n' % __FILE__ + sys.exit(1) + + adb_cmd = 'adb' + remote_path = '/data/local/webapps' + if len(sys.argv) >= 2: + adb_cmd = sys.argv[1] + if len(sys.argv) >= 3: + remote_path = sys.argv[2] + + install_gaia() |