Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/build/install-gaia.py
blob: 9ff2ae0ac1688c7313f07eccdbb0dfbe6686297e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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()