chiark / gitweb /
Make the server tools an installable package (with distutils) - wip
[fdroidserver.git] / fdroidserver / checkupdates.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # checkupdates.py - part of the FDroid server tools
5 # Copyright (C) 2010-12, Ciaran Gultnieks, ciaran@ciarang.com
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU Affero General Public License for more details.
16 #
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 import sys
21 import os
22 import shutil
23 import re
24 import urllib
25 import time
26 from optparse import OptionParser
27 import HTMLParser
28 import common
29
30
31 # Check for a new version by looking at the AndroidManifest.xml at the HEAD
32 # of the source repo. Whether this can be used reliably or not depends on
33 # the development procedures used by the project's developers. Use it with
34 # caution, because it's inappropriate for many projects.
35 # Returns (None, "a message") if this didn't work, or (version, vercode) for
36 # the details of the current version.
37 def check_repomanifest(app):
38
39     try:
40
41         build_dir = 'build/' + app['id']
42
43         if app['Repo Type'] != 'git':
44             return (None, 'RepoManifest update mode only works for git repositories currently')
45
46         # Set up vcs interface and make sure we have the latest code...
47         vcs = common.getvcs(app['Repo Type'], app['Repo'], build_dir)
48         vcs.gotorevision('origin/master')
49
50         if len(app['builds']) == 0:
51             return (None, "Can't use RepoManifest with no builds defined")
52
53         manifest = build_dir
54         if app['builds'][-1].has_key('subdir'):
55             manifest = os.path.join(manifest, app['builds'][-1]['subdir'])
56         manifest = os.path.join(manifest, 'AndroidManifest.xml')
57
58         version, vercode, package = common.parse_androidmanifest(manifest)
59         if not package:
60             return (None, "Couldn't find ipackage ID")
61         if package != app['id']:
62             return (None, "Package ID mismatch")
63         if not version:
64             return (None,"Couldn't find latest version name")
65         if not vercode:
66             return (None,"Couldn't find latest version code")
67
68         return (version, vercode)
69
70     except BuildException as be:
71         msg = "Could not scan app %s due to BuildException: %s" % (app['id'], be)
72         return (None, msg)
73     except VCSException as vcse:
74         msg = "VCS error while scanning app %s: %s" % (app['id'], vcse)
75         return (None, msg)
76     except Exception:
77         msg = "Could not scan app %s due to unknown error: %s" % (app['id'], traceback.format_exc())
78         return (None, msg)
79
80
81 # Check for a new version by looking at the Google market.
82 # Returns (None, "a message") if this didn't work, or (version, vercode) for
83 # the details of the current version.
84 def check_market(app):
85     time.sleep(10)
86     url = 'http://market.android.com/details?id=' + app['id']
87     req = urllib.urlopen(url)
88     if req.getcode() == 404:
89         return (None, 'Not in market')
90     elif req.getcode() != 200:
91         return (None, 'Return code ' + str(req.getcode()))
92     page = req.read()
93
94     version = None
95     vercode = None
96
97     m = re.search('<dd itemprop="softwareVersion">([^>]+)</dd>', page)
98     if m:
99         html_parser = HTMLParser.HTMLParser()
100         version = html_parser.unescape(m.group(1))
101
102     if version == 'Varies with device':
103         return (None, 'Device-variable version, cannot use this method')
104
105     m = re.search('data-paramValue="(\d+)"><div class="goog-menuitem-content">Latest Version<', page)
106     if m:
107         vercode = m.group(1)
108
109     if not vercode:
110         return (None, "Couldn't find version code")
111     if not version:
112         return (None, "Couldn't find version")
113     return (version, vercode)
114
115
116
117 def main():
118
119     #Read configuration...
120     execfile('config.py', globals())
121
122     # Parse command line...
123     parser = OptionParser()
124     parser.add_option("-v", "--verbose", action="store_true", default=False,
125                       help="Spew out even more information than normal")
126     parser.add_option("-p", "--package", default=None,
127                       help="Build only the specified package")
128     (options, args) = parser.parse_args()
129
130     # Get all apps...
131     apps = common.read_metadata(options.verbose)
132
133     for app in apps:
134
135         if options.package and options.package != app['id']:
136             # Silent skip...
137             pass
138         else:
139             print "Processing " + app['id'] + '...'
140
141             mode = app['Update Check Mode']
142             if mode == 'Market':
143                 (version, vercode) = check_market(app)
144             elif mode == 'RepoManifest':
145                 (version, vercode) = check_repomanifest(app)
146             elif mode == 'None':
147                 version = None
148                 vercode = 'Checking disabled'
149             else:
150                 version = None
151                 vercode = 'Invalid update check method'
152
153             if not version:
154                 print "..." + vercode
155             elif vercode == app['Current Version Code'] and version == app['Current Version']:
156                 print "...up to date"
157             else:
158                 print '...updating to version:' + version + ' vercode:' + vercode
159                 app['Current Version'] = version
160                 app['Current Version Code'] = vercode
161                 metafile = os.path.join('metadata', app['id'] + '.txt')
162                 common.write_metadata(metafile, app)
163
164     print "Finished."
165
166 if __name__ == "__main__":
167     main()
168