chiark / gitweb /
Merge branch 'use_date_from_apk_in_known_apks' into 'master'
authorCiaran Gultnieks <ciaran@ciarang.com>
Wed, 13 Jul 2016 11:43:33 +0000 (11:43 +0000)
committerCiaran Gultnieks <ciaran@ciarang.com>
Wed, 13 Jul 2016 11:43:33 +0000 (11:43 +0000)
Pass a date from APK to KnownApks.recordapk()

... if --use-date-from-apks option is used.

Essentially, it just expands influence of `--use-date-from-apks` option to `stats/known_apks.txt`.

See merge request !141

1  2 
fdroidserver/common.py
fdroidserver/update.py

diff --combined fdroidserver/common.py
index 2c2bb4e062b77472f7a2deb67d107897de7bc567,88477b1836ffcacec38a3b53b442ad257c9e77ef..ce81b3855ba2f901690afc07d5a6284d4e3d189e
@@@ -58,7 -58,7 +58,7 @@@ default_config = 
          'r9b': None,
          'r10e': "$ANDROID_NDK",
      },
 -    'build_tools': "23.0.3",
 +    'build_tools': "24.0.0",
      'force_build_tools': False,
      'java_paths': None,
      'ant': "ant",
@@@ -1570,9 -1570,11 +1570,11 @@@ class KnownApks
  
      # Record an apk (if it's new, otherwise does nothing)
      # Returns the date it was added.
-     def recordapk(self, apk, app):
+     def recordapk(self, apk, app, default_date=None):
          if apk not in self.apks:
-             self.apks[apk] = (app, time.gmtime(time.time()))
+             if default_date is None:
+                 default_date = time.gmtime(time.time())
+             self.apks[apk] = (app, default_date)
              self.changed = True
          _, added = self.apks[apk]
          return added
@@@ -1794,8 -1796,7 +1796,8 @@@ def set_FDroidPopen_env(build=None)
      set up the environment variables for the build environment
  
      There is only a weak standard, the variables used by gradle, so also set
 -    up the most commonly used environment variables for SDK and NDK
 +    up the most commonly used environment variables for SDK and NDK.  Also, if
 +    there is no locale set, this will set the locale (e.g. LANG) to en_US.UTF-8.
      '''
      global env, orig_path
  
          for k, v in config['java_paths'].items():
              env['JAVA%s_HOME' % k] = v
  
 +    missinglocale = True
 +    for k, v in env.items():
 +        if k == 'LANG' and v != 'C':
 +            missinglocale = False
 +        elif k == 'LC_ALL':
 +            missinglocale = False
 +    if missinglocale:
 +        env['LANG'] = 'en_US.UTF-8'
 +
      if build is not None:
          path = build.ndk_path()
          paths = orig_path.split(os.pathsep)
diff --combined fdroidserver/update.py
index dba1a40979b939c46288ee48db24e411c7c65340,b35f9287c4fcd1c0839adc32d646e30f8fb031e1..e4d2ff3c2e04e6eea956541aa33e526cfb021e5e
@@@ -323,10 -323,8 +323,10 @@@ def resize_icon(iconpath, density)
      if not os.path.isfile(iconpath):
          return
  
 +    fp = None
      try:
 -        im = Image.open(iconpath)
 +        fp = open(iconpath, 'rb')
 +        im = Image.open(fp)
          size = dpi_to_px(density)
  
          if any(length > size for length in im.size):
      except Exception as e:
          logging.error("Failed resizing {0} - {1}".format(iconpath, e))
  
 +    finally:
 +        if fp:
 +            fp.close()
 +
  
  def resize_all_icons(repodirs):
      """Resize all icons that exceed the max size
@@@ -411,90 -405,6 +411,90 @@@ def getsig(apkpath)
      return hashlib.md5(hexlify(cert_encoded)).hexdigest()
  
  
 +def get_icon_bytes(apkzip, iconsrc):
 +    '''ZIP has no official encoding, UTF-* and CP437 are defacto'''
 +    try:
 +        return apkzip.read(iconsrc)
 +    except KeyError:
 +        return apkzip.read(iconsrc.encode('utf-8').decode('cp437'))
 +
 +
 +def sha256sum(filename):
 +    '''Calculate the sha256 of the given file'''
 +    sha = hashlib.sha256()
 +    with open(filename, 'rb') as f:
 +        while True:
 +            t = f.read(16384)
 +            if len(t) == 0:
 +                break
 +            sha.update(t)
 +    return sha.hexdigest()
 +
 +
 +def insert_obbs(repodir, apps, apks):
 +    """Scans the .obb files in a given repo directory and adds them to the
 +    relevant APK instances.  OBB files have versionCodes like APK
 +    files, and they are loosely associated.  If there is an OBB file
 +    present, then any APK with the same or higher versionCode will use
 +    that OBB file.  There are two OBB types: main and patch, each APK
 +    can only have only have one of each.
 +
 +    https://developer.android.com/google/play/expansion-files.html
 +
 +    :param repodir: repo directory to scan
 +    :param apps: list of current, valid apps
 +    :param apks: current information on all APKs
 +
 +    """
 +
 +    def obbWarnDelete(f, msg):
 +        logging.warning(msg + f)
 +        if options.delete_unknown:
 +            logging.error("Deleting unknown file: " + f)
 +            os.remove(f)
 +
 +    obbs = []
 +    java_Integer_MIN_VALUE = -pow(2, 31)
 +    for f in glob.glob(os.path.join(repodir, '*.obb')):
 +        obbfile = os.path.basename(f)
 +        # obbfile looks like: [main|patch].<expansion-version>.<package-name>.obb
 +        chunks = obbfile.split('.')
 +        if chunks[0] != 'main' and chunks[0] != 'patch':
 +            obbWarnDelete(f, 'OBB filename must start with "main." or "patch.": ')
 +            continue
 +        if not re.match(r'^-?[0-9]+$', chunks[1]):
 +            obbWarnDelete('The OBB version code must come after "' + chunks[0] + '.": ')
 +            continue
 +        versioncode = int(chunks[1])
 +        packagename = ".".join(chunks[2:-1])
 +
 +        highestVersionCode = java_Integer_MIN_VALUE
 +        if packagename not in apps.keys():
 +            obbWarnDelete(f, "OBB's packagename does not match a supported APK: ")
 +            continue
 +        for apk in apks:
 +            if packagename == apk['id'] and apk['versioncode'] > highestVersionCode:
 +                highestVersionCode = apk['versioncode']
 +        if versioncode > highestVersionCode:
 +            obbWarnDelete(f, 'OBB file has newer versioncode(' + str(versioncode)
 +                          + ') than any APK: ')
 +            continue
 +        obbsha256 = sha256sum(f)
 +        obbs.append((packagename, versioncode, obbfile, obbsha256))
 +
 +    for apk in apks:
 +        for (packagename, versioncode, obbfile, obbsha256) in sorted(obbs, reverse=True):
 +            if versioncode <= apk['versioncode'] and packagename == apk['id']:
 +                if obbfile.startswith('main.') and 'obbMainFile' not in apk:
 +                    apk['obbMainFile'] = obbfile
 +                    apk['obbMainFileSha256'] = obbsha256
 +                elif obbfile.startswith('patch.') and 'obbPatchFile' not in apk:
 +                    apk['obbPatchFile'] = obbfile
 +                    apk['obbPatchFileSha256'] = obbsha256
 +            if 'obbMainFile' in apk and 'obbPatchFile' in apk:
 +                break
 +
 +
  def scan_apks(apps, apkcache, repodir, knownapks, use_date_from_apk=False):
      """Scan the apks in the given repo directory.
  
      icon_pat = re.compile(".*application-icon-([0-9]+):'([^']+?)'.*")
      icon_pat_nodpi = re.compile(".*icon='([^']+?)'.*")
      sdkversion_pat = re.compile(".*'([0-9]*)'.*")
 -    string_pat = re.compile(".*'([^']*)'.*")
 +    string_pat = re.compile(".* name='([^']*)'.*")
      for apkfile in glob.glob(os.path.join(repodir, '*.apk')):
  
          apkfilename = apkfile[len(repodir) + 1:]
              logging.critical("Spaces in filenames are not allowed.")
              sys.exit(1)
  
 -        # Calculate the sha256...
 -        sha = hashlib.sha256()
 -        with open(apkfile, 'rb') as f:
 -            while True:
 -                t = f.read(16384)
 -                if len(t) == 0:
 -                    break
 -                sha.update(t)
 -            shasum = sha.hexdigest()
 +        shasum = sha256sum(apkfile)
  
          usecache = False
          if apkfilename in apkcache:
  
              # Check for debuggable apks...
              if common.isApkDebuggable(apkfile, config):
 -                logging.warn('{0} is set to android:debuggable="true"'.format(apkfile))
 +                logging.warning('{0} is set to android:debuggable="true"'.format(apkfile))
  
              # Get the signature (or md5 of, to be precise)...
              logging.debug('Getting signature of {0}'.format(apkfile))
  
                  try:
                      with open(icondest, 'wb') as f:
 -                        f.write(apkzip.read(iconsrc))
 +                        f.write(get_icon_bytes(apkzip, iconsrc))
                      apk['icons'][density] = iconfilename
  
                  except:
                  iconpath = os.path.join(
                      get_icon_dir(repodir, '0'), iconfilename)
                  with open(iconpath, 'wb') as f:
 -                    f.write(apkzip.read(iconsrc))
 +                    f.write(get_icon_bytes(apkzip, iconsrc))
                  try:
                      im = Image.open(iconpath)
                      dpi = px_to_dpi(im.size[0])
                      get_icon_dir(repodir, last_density), iconfilename)
                  iconpath = os.path.join(
                      get_icon_dir(repodir, density), iconfilename)
 +                fp = None
                  try:
 -                    im = Image.open(last_iconpath)
 -                except:
 -                    logging.warn("Invalid image file at %s" % last_iconpath)
 -                    continue
 +                    fp = open(last_iconpath, 'rb')
 +                    im = Image.open(fp)
  
 -                size = dpi_to_px(density)
 +                    size = dpi_to_px(density)
  
 -                im.thumbnail((size, size), Image.ANTIALIAS)
 -                im.save(iconpath, "PNG")
 -                empty_densities.remove(density)
 +                    im.thumbnail((size, size), Image.ANTIALIAS)
 +                    im.save(iconpath, "PNG")
 +                    empty_densities.remove(density)
 +                except:
 +                    logging.warning("Invalid image file at %s" % last_iconpath)
 +                finally:
 +                    if fp:
 +                        fp.close()
  
              # Then just copy from the highest resolution available
              last_density = None
                  shutil.copyfile(baseline,
                                  os.path.join(get_icon_dir(repodir, '0'), iconfilename))
  
+             if use_date_from_apk and manifest.date_time[1] != 0:
+                 default_date_param = datetime(*manifest.date_time).utctimetuple()
+             else:
+                 default_date_param = None
              # Record in known apks, getting the added date at the same time..
-             added = knownapks.recordapk(apk['apkname'], apk['id'])
+             added = knownapks.recordapk(apk['apkname'], apk['id'], default_date=default_date_param)
              if added:
-                 if use_date_from_apk and manifest.date_time[1] != 0:
-                     added = datetime(*manifest.date_time).timetuple()
-                     logging.debug("Using date from APK")
                  apk['added'] = added
  
              apkcache[apkfilename] = apk
@@@ -1039,10 -954,6 +1040,10 @@@ def make_index(apps, sortedids, apks, r
                  addElement('targetSdkVersion', str(apk['targetSdkVersion']), doc, apkel)
              if 'maxSdkVersion' in apk:
                  addElement('maxsdkver', str(apk['maxSdkVersion']), doc, apkel)
 +            addElementNonEmpty('obbMainFile', apk.get('obbMainFile'), doc, apkel)
 +            addElementNonEmpty('obbMainFileSha256', apk.get('obbMainFileSha256'), doc, apkel)
 +            addElementNonEmpty('obbPatchFile', apk.get('obbPatchFile'), doc, apkel)
 +            addElementNonEmpty('obbPatchFileSha256', apk.get('obbPatchFileSha256'), doc, apkel)
              if 'added' in apk:
                  addElement('added', time.strftime('%Y-%m-%d', apk['added']), doc, apkel)
              addElementNonEmpty('permissions', ','.join(apk['permissions']), doc, apkel)
@@@ -1228,7 -1139,7 +1229,7 @@@ def main()
      parser.add_argument("-c", "--create-metadata", action="store_true", default=False,
                          help="Create skeleton metadata files that are missing")
      parser.add_argument("--delete-unknown", action="store_true", default=False,
 -                        help="Delete APKs without metadata from the repo")
 +                        help="Delete APKs and/or OBBs without metadata from the repo")
      parser.add_argument("-b", "--buildreport", action="store_true", default=False,
                          help="Report on build data status")
      parser.add_argument("-i", "--interactive", default=False, action="store_true",
      if newmetadata:
          apps = metadata.read_metadata()
  
 +    insert_obbs(repodirs[0], apps, apks)
 +
      # Scan the archive repo for apks as well
      if len(repodirs) > 1:
          archapks, cc = scan_apks(apps, apkcache, repodirs[1], knownapks, options.use_date_from_apk)