diff options
| author | ced <> | 2007-07-30 13:35:27 +0000 |
|---|---|---|
| committer | ced <> | 2007-07-30 13:35:27 +0000 |
| commit | 792fc3cf97ad04602ac4d8e299c216a19cda7bf0 (patch) | |
| tree | 6e92decdc2690ae0024317fd3b7c1b9e8363bfca /bin/addons/base/module/module.py | |
| parent | 5d123a10f5513c98d2a1f77ead01fbda2ec1097e (diff) | |
base,kernel,base_module_publish: improve publication of module, add repository, etc...
- add repository for module
- improve version number
- add check to remove module
- can not upload module with the same version number
- add version compare from portage (gentoo)
- add release in context of eval
- add active = True to base module
- add wizard to update all installed modules
- add check to not upload module that is not installed
- add posibility to include source
- fix license
- now load zip module in preference
bzr revid: ced-e1b031d238126e8060b0e2cb6f87bb93cca5a343
Diffstat (limited to 'bin/addons/base/module/module.py')
| -rw-r--r-- | bin/addons/base/module/module.py | 275 |
1 files changed, 231 insertions, 44 deletions
diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py index cdbeebae06c..1d3974c6689 100644 --- a/bin/addons/base/module/module.py +++ b/bin/addons/base/module/module.py @@ -32,6 +32,114 @@ import os import tools from osv import fields, osv, orm import zipfile +import release + +ver_regexp = re.compile("^(\\d+)((\\.\\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\\d*)*)(-r(\\d+))?$") +suffix_regexp = re.compile("^(alpha|beta|rc|pre|p)(\\d*)$") + +def vercmp(ver1, ver2): + """ + Compare two versions + Take from portage_versions.py + @param ver1: version to compare with + @type ver1: string (example "1.2-r3") + @param ver2: version to compare again + @type ver2: string (example "2.1-r1") + @rtype: None or float + @return: + 1. position if ver1 is greater than ver2 + 2. negative if ver1 is less than ver2 + 3. 0 if ver1 equals ver2 + 4. None if ver1 or ver2 are invalid + """ + + match1 = ver_regexp.match(ver1) + match2 = ver_regexp.match(ver2) + + if not match1 or not match1.groups(): + return None + if not match2 or not match2.groups(): + return None + + list1 = [int(match1.group(1))] + list2 = [int(match2.group(1))] + + if len(match1.group(2)) or len(match2.group(2)): + vlist1 = match1.group(2)[1:].split(".") + vlist2 = match2.group(2)[1:].split(".") + for i in range(0, max(len(vlist1), len(vlist2))): + # Implicit .0 is given -1, so 1.0.0 > 1.0 + # would be ambiguous if two versions that aren't literally equal + # are given the same value (in sorting, for example). + if len(vlist1) <= i or len(vlist1[i]) == 0: + list1.append(-1) + list2.append(int(vlist2[i])) + elif len(vlist2) <= i or len(vlist2[i]) == 0: + list1.append(int(vlist1[i])) + list2.append(-1) + # Let's make life easy and use integers unless we're forced to use floats + elif (vlist1[i][0] != "0" and vlist2[i][0] != "0"): + list1.append(int(vlist1[i])) + list2.append(int(vlist2[i])) + # now we have to use floats so 1.02 compares correctly against 1.1 + else: + list1.append(float("0."+vlist1[i])) + list2.append(float("0."+vlist2[i])) + # and now the final letter + if len(match1.group(4)): + list1.append(ord(match1.group(4))) + if len(match2.group(4)): + list2.append(ord(match2.group(4))) + + for i in range(0, max(len(list1), len(list2))): + if len(list1) <= i: + return -1 + elif len(list2) <= i: + return 1 + elif list1[i] != list2[i]: + return list1[i] - list2[i] + + # main version is equal, so now compare the _suffix part + list1 = match1.group(5).split("_")[1:] + list2 = match2.group(5).split("_")[1:] + + for i in range(0, max(len(list1), len(list2))): + # Implicit _p0 is given a value of -1, so that 1 < 1_p0 + if len(list1) <= i: + s1 = ("p","-1") + else: + s1 = suffix_regexp.match(list1[i]).groups() + if len(list2) <= i: + s2 = ("p","-1") + else: + s2 = suffix_regexp.match(list2[i]).groups() + if s1[0] != s2[0]: + return suffix_value[s1[0]] - suffix_value[s2[0]] + if s1[1] != s2[1]: + # it's possible that the s(1|2)[1] == '' + # in such a case, fudge it. + try: + r1 = int(s1[1]) + except ValueError: + r1 = 0 + try: + r2 = int(s2[1]) + except ValueError: + r2 = 0 + if r1 - r2: + return r1 - r2 + + # the suffix part is equal to, so finally check the revision + if match1.group(9): + r1 = int(match1.group(9)) + else: + r1 = 0 + if match2.group(9): + r2 = int(match2.group(9)) + else: + r2 = 0 + return r1 - r2 + class module_repository(osv.osv): _name = "ir.module.repository" @@ -39,7 +147,18 @@ class module_repository(osv.osv): _columns = { 'name': fields.char('Name', size=128), 'url': fields.char('Url', size=256, required=True), + 'sequence': fields.integer('Sequence', required=True), + 'filter': fields.char('Filter', size=128, required=True, + help='Regexp to search module on the repository webpage:\n' + '- The first parenthesis must match the name of the module.\n' + '- The second parenthesis must match all the version number.\n' + '- The last parenthesis must match the extension of the module.'), } + _defaults = { + 'sequence': lambda *a: 5, + 'filter': lambda *a: 'href="([a-zA-Z0-9_]+)-('+release.version.rsplit('.', 1)[0]+'.(\\d+)((\\.\\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\\d*)*)(-r(\\d+))?)(\.zip)"', + } + _order = "sequence" module_repository() class module_category(osv.osv): @@ -73,6 +192,8 @@ class module(osv.osv): f = tools.file_open(os.path.join(tools.config['addons_path'], name, '__terp__.py')) data = f.read() info = eval(data) + if name == 'version': + info = release.version.rsplit('.', 1)[0] + '.' + info f.close() except: return {} @@ -82,7 +203,8 @@ class module(osv.osv): res = {} for m in self.browse(cr, uid, ids): if m.state in ('installed', 'to upgrade', 'to remove'): - res[m.id] = self.get_module_info(m.name).get('version', False) + res[m.id] = release.version.rsplit('.', 1)[0] + '.' + \ + self.get_module_info(m.name).get('version', False) else: res[m.id] = '' return res @@ -94,10 +216,12 @@ class module(osv.osv): 'description': fields.text("Description", readonly=True), 'author': fields.char("Author", size=128, readonly=True), 'website': fields.char("Website", size=256, readonly=True), - 'installed_version': fields.function(_get_installed_version, method=True, string='Installed version', type='char'), + 'installed_version': fields.function(_get_installed_version, method=True, + string='Installed version', type='char'), 'latest_version': fields.char('Latest version', size=64, readonly=True), - 'url': fields.char('URL', size=128, readonly=True), - 'dependencies_id': fields.one2many('ir.module.module.dependency', 'module_id', 'Dependencies'), + 'url': fields.char('URL', size=128), + 'dependencies_id': fields.one2many('ir.module.module.dependency', + 'module_id', 'Dependencies', readonly=True), 'state': fields.selection([ ('uninstallable','Uninstallable'), ('uninstalled','Not Installed'), @@ -123,6 +247,17 @@ class module(osv.osv): ('name_uniq', 'unique (name)', 'The name of the module must be unique !') ] + def unlink(self, cr, uid, ids, context=None): + if not ids: + return True + if isinstance(ids, (int, long)): + ids = [ids] + for mod in self.read(cr, uid, ids, ['state'], context): + if mod['state'] in ('installed', 'to upgrade', 'to remove', 'to install'): + raise orm.except_orm('Error', + 'You try to remove a module that is installed or will be installed') + return super(module, self).unlink(cr, uid, ids, context=context) + def state_change(self, cr, uid, ids, newstate, context={}, level=50): if level<1: raise 'Recursion error in modules dependencies !' @@ -131,14 +266,31 @@ class module(osv.osv): mdemo = True for dep in module.dependencies_id: ids2 = self.search(cr, uid, [('name','=',dep.name)]) - mdemo = self.state_change(cr, uid, ids2, newstate, context, level-1) and mdemo + mdemo = self.state_change(cr, uid, ids2, newstate, context, level-1,)\ + and mdemo if not module.dependencies_id: mdemo = module.demo - if module.state=='uninstalled': + if module.state == 'uninstalled': self.write(cr, uid, [module.id], {'state': newstate, 'demo':mdemo}) demo = demo and mdemo return demo + def state_upgrade(self, cr, uid, ids, newstate, context=None, level=50): + dep_obj = self.pool.get('ir.module.module.dependency') + if level<1: + raise 'Recursion error in modules dependencies !' + for module in self.browse(cr, uid, ids): + dep_ids = dep_obj.search(cr, uid, [('name', '=', module.name)]) + if dep_ids: + ids2 = [] + for dep in dep_obj.browse(cr, uid, dep_ids): + if dep.module_id.state != 'to upgrade': + ids2.append(dep.module_id.id) + self.state_upgrade(cr, uid, ids2, newstate, context, level) + if module.state == 'installed': + self.write(cr, uid, module.id, {'state': newstate}) + return True + def button_install(self, cr, uid, ids, context={}): return self.state_change(cr, uid, ids, 'to install', context) @@ -165,9 +317,8 @@ class module(osv.osv): def button_uninstall_cancel(self, cr, uid, ids, context={}): self.write(cr, uid, ids, {'state': 'installed'}) return True - def button_upgrade(self, cr, uid, ids, context={}): - self.write(cr, uid, ids, {'state': 'to upgrade'}) - return True + def button_upgrade(self, cr, uid, ids, context=None): + return self.state_upgrade(cr, uid, ids, 'to upgrade', context) def button_upgrade_cancel(self, cr, uid, ids, context={}): self.write(cr, uid, ids, {'state': 'installed'}) return True @@ -190,6 +341,7 @@ class module(osv.osv): def update_list(self, cr, uid, context={}): robj = self.pool.get('ir.module.repository') adp = tools.config['addons_path'] + res = [0, 0] # [update, add] # iterate through installed modules and mark them as being so for name in os.listdir(adp): @@ -198,20 +350,28 @@ class module(osv.osv): mod_name=name[:-4] ids = self.search(cr, uid, [('name','=',mod_name)]) if ids: + id = ids[0] + mod = self.browse(cr, uid, id) terp = self.get_module_info(mod_name) - if terp.get('installable', True) and self.read(cr, uid, ids, ['state'])[0]['state'] == 'uninstallable': - self.write(cr, uid, ids, {'state': 'uninstalled'}) - self.write(cr, uid, ids, { + if terp.get('installable', True) and mod.state == 'uninstallable': + self.write(cr, uid, id, {'state': 'uninstalled'}) + if vercmp(terp.get('version', ''), mod.latest_version) > 0: + self.write(cr, uid, id, { + 'latest_version': terp.get('version')}) + res[0] += 1 + self.write(cr, uid, id, { 'description': terp.get('description', ''), 'shortdesc': terp.get('name', ''), 'author': terp.get('author', 'Unknown'), 'website': terp.get('website', ''), - 'latest_version': terp.get('version', ''), 'license': terp.get('license', 'GPL-2'), }) - cr.execute('DELETE FROM ir_module_module_dependency where module_id = %d', (ids[0],)) - self._update_dependencies(cr, uid, ids[0], terp.get('depends', [])) - self._update_category(cr, uid, ids[0], terp.get('category', 'Uncategorized')) + cr.execute('DELETE FROM ir_module_module_dependency\ + WHERE module_id = %d', (id,)) + self._update_dependencies(cr, uid, ids[0], terp.get('depends', + [])) + self._update_category(cr, uid, ids[0], terp.get('category', + 'Uncategorized')) continue terp_file = os.path.join(adp, name, '__terp__.py') mod_path = os.path.join(adp, name) @@ -219,16 +379,15 @@ class module(osv.osv): terp = self.get_module_info(mod_name) if not terp or not terp.get('installable', True): continue - try: + if not os.path.isfile(os.path.join(adp, mod_name+'.zip')): import imp # XXX must restrict to only addons paths - imp.load_module(name, *imp.find_module(name)) - except ImportError: + imp.load_module(name, *imp.find_module(mod_name)) + else: import zipimport - mod_path = os.path.join(adp, name) + mod_path = os.path.join(adp, mod_name+'.zip') zimp = zipimport.zipimporter(mod_path) zimp.load_module(mod_name) - version = terp.get('version', False) id = self.create(cr, uid, { 'name': mod_name, 'state': 'uninstalled', @@ -239,16 +398,27 @@ class module(osv.osv): 'latest_version': terp.get('version', ''), 'license': terp.get('license', 'GPL-2'), }) + res[1] += 1 self._update_dependencies(cr, uid, id, terp.get('depends', [])) self._update_category(cr, uid, id, terp.get('category', 'Uncategorized')) - # make the list of all installable modules for repository in robj.browse(cr, uid, robj.search(cr, uid, [])): index_page = urllib.urlopen(repository.url).read() - modules = re.findall('.*<a href="([a-zA-Z0-9.\-]+)_([a-zA-Z0-9.\-]+)\.tar\.gz">.*', index_page) - for name, version in modules: - # TODO: change this using urllib - url = os.path.join(repository.url, name + '_' + version + ".tar.gz") + modules = re.findall(repository.filter, index_page, re.I+re.M) + mod_sort = {} + for m in modules: + name = m[0] + version = m[1] + extension = m[-1] + if version == 'x': # 'x' version was a mistake + version = '0' + if name in mod_sort: + if vercmp(version, mod_sort[name][0]) <= 0: + continue + mod_sort[name] = [version, extension] + for name in mod_sort.keys(): + version, extension = mod_sort[name] + url = repository.url+'/'+name+'-'+version+extension ids = self.search(cr, uid, [('name','=',name)]) if not ids: self.create(cr, uid, { @@ -257,11 +427,42 @@ class module(osv.osv): 'url': url, 'state': 'uninstalled', }) + res[1] += 1 else: - for r in self.read(cr, uid, ids, ['latest_version']): - if r['latest_version'] < version: - self.write(cr, uid, [r['id']], {'latest_version': version, 'url':url}) - return True + id = ids[0] + latest_version = self.read(cr, uid, id, ['latest_version'])\ + ['latest_version'] + if latest_version == 'x': # 'x' version was a mistake + latest_version = '0' + c = vercmp(version, latest_version) + if c > 0: + self.write(cr, uid, id, + {'latest_version': version, 'url': url}) + res[0] += 1 + elif c == 0: + self.write(cr, uid, id, {'url': url}) + return res + + def download(self, cr, uid, ids, context=None): + adp = tools.config['addons_path'] + for mod in self.browse(cr, uid, ids, context=context): + if not mod.url: + continue + match = re.search('-([a-zA-Z0-9\._-]+)(\.zip)', mod.url, re.I) + version = '0' + if match: + version = match.group(1) + if vercmp(mod.installed_version or '0', version) >= 0: + continue + zipfile = urllib.urlopen(mod.url).read() + fname = os.path.join(adp, mod.name+'.zip') + try: + fp = file(fname, 'wb') + fp.write(zipfile) + fp.close() + except IOError, e: + raise orm.except_orm('Error', 'Can not create the module file:\n %s' + % (fname,)) def _update_dependencies(self, cr, uid, id, depends=[]): for d in depends: @@ -305,8 +506,6 @@ class module_dependency(osv.osv): _columns = { 'name': fields.char('Name', size=128), 'module_id': fields.many2one('ir.module.module', 'Module', select=True), - #'module_dest_id': fields.many2one('ir.module.module', 'Module'), - 'version_pattern': fields.char('Required Version', size=128), 'state': fields.function(_state, method=True, type='selection', selection=[ ('uninstallable','Uninstallable'), ('uninstalled','Not Installed'), @@ -317,17 +516,5 @@ class module_dependency(osv.osv): ('unknown', 'Unknown'), ], string='State', readonly=True), } - # returns the ids of module version records which match all dependencies - # [version_id, ...] - def resolve(self, cr, uid, ids): - vobj = self.pool.get('ir.module.module.version') - objs = self.browse(cr, uid, ids) - res = {} - for o in objs: - pattern = o.version_pattern and eval(o.version_pattern) or [] - res[o.id] = vobj.search(cr, uid, [('module','=',o.module.id)]+pattern) - #TODO: add smart dependencies resolver here - # it should compute the best version for each module - return [r[0] for r in res.itervalues()] module_dependency() |
