############################################################################## # # Copyright (c) 2005 TINY SPRL. (http://tiny.be) All Rights Reserved. # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential # consequences resulting from its eventual inadequacies and bugs # End users who are looking for a ready-to-use solution with commercial # garantees and support are strongly adviced to contract a Free Software # Service Company # # This program is Free Software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ############################################################################## import tarfile import re import urllib2 import os import shutil from sets import Set from osv import fields, osv import tools class module(osv.osv): _name = "module.module" _description = "Module" def _costs_get(self, cr, uid, ids, prop=None, unknown_none=None, unknown_dict={}): dossiers = self.browse(cr, uid, ids) res = {} for d in dossiers: costs = [l.amount_costs for l in d.lot_id] cost_amount = reduce(lambda x, y: x+y, costs, 0.0) res[d.id] = cost_amount return res _columns = { 'name': fields.char("Name", size=128, readonly=True), 'shortdesc': fields.char('Short description', size=256, readonly=True), 'description': fields.text("Description", readonly=True), 'author': fields.char("Author", size=128, readonly=True), 'website': fields.char("Website", size=256, readonly=True), # 'running_version': fields.function(_get_installed_version, method=True, string='Installed version'), # 'installed_version': fields.function(_get_installed_version, method=True, string='Installed version'), 'latest_version': fields.many2one('module.module.version', 'Latest version'), 'versions': fields.one2many('module.module.version', 'module', 'Versions', readonly=True), 'state': fields.selection([('uninstalled', 'Uninstalled'), ('preinstall','To install'),('installed','Installed'),('running','Running')], 'State'), # 'active': fields.boolean('Active'), } # update the list of available packages def update(self, cr, uid, ids, *args): vobj = self.pool.get('module.module.version') # update installed_version # get the index page containing available packages from the server index_page = urllib2.urlopen('http://www.tinyerp.org/download/modules_test').read() # print index_page # parses it modules = re.findall('.*.*', index_page) # create module.module records and module.module.version as needed for name, version in modules: print "name", name, "version", version # open module package on the remote server and extract its __terp__.py url = 'http://www.tinyerp.org/download/modules_test/' + name + '_' + version + ".tar.gz" tar = tarfile.open(mode="r|gz", fileobj=urllib2.urlopen(url)) info = {} # we need to go through the whole tar file, because we use a stream and we # are not allowed to search backward in the stream, so we can't extract one # particular file directly for tarinfo in tar: if tarinfo.name.endswith('__terp__.py'): info = eval(tar.extractfile(tarinfo).read()) break tar.close() print info ids = self.search(cr, uid, [('name','=',name)]) print ids if not ids: id = self.create(cr, uid, { 'name': name, 'shortdesc': info.get('name', False), 'description': info.get('description', False), 'author': info.get('author', False), 'website': info.get('website', False), 'state': 'uninstalled', }) print "module_id", id else: assert len(ids)==1, "There shouldn't be two modules with the same name" id = ids[0] version_ids = vobj.search(cr, uid, [('module','=',id),('name','=',version)]) print version_ids if not version_ids: version_id = vobj.create(cr, uid, { 'name': version, 'module': id, 'state': 'uninstalled', }) print "version_id", version_id # update latest_version #TODO: compute latest version self.write(cr, uid, [id], {'latest_version': version_id}) # else: # assert len(version_ids)==1, "There shouldn't be two versions with the same name" # version_id = version_ids[0] return True def install(self, cr, uid, ids, *args): objs = self.browse(cr, uid, ids) vobj = self.pool.get('module.module.version') # get the id of latest version for each module version_ids = Set([o.latest_version.id for o in objs]) # for o in objs: # version_ids.add() # version_ids = reduce(lambda dic, o: dic.update({o.latest_version.id:True}), objs, {}) print "version_ids", version_ids # add the list of dependencies dependencies_ids = vobj.get_dependencies(cr, uid, list(version_ids)) print "depends", dependencies_ids version_ids.update(dependencies_ids) # version_ids = reduce(lambda dic, dep: dic.update({dep:True}), dependencies_ids, version_ids) print "version_ids2", version_ids # remove existing version of modules self.remove(cr, uid, ids) # install all selected modules and their dependencies vobj.install(cr, uid, list(version_ids)) return True # remove existing version of modules if they exist def remove(self, cr, uid, ids, *args): objs = self.browse(cr, uid, ids) adp = tools.config['addons_path'] addons = os.listdir(adp) for o in objs: if o.name in addons: shutil.rmtree(os.path.join(adp, o.name)) return True module() class module_version(osv.osv): _name = "module.module.version" _description = "Module Version" _columns = { 'name': fields.char('Name', size=64), 'module': fields.many2one('module.module', "Module"), 'dependencies': fields.one2many('module.module.dependency', 'version', 'Dependencies'), 'state': fields.selection([('uninstalled','Uninstalled'), ('preinstall','To install'), ('installed','Installed'), ('running','Running')], 'State'), } def install(self, cr, uid, ids, *args): print "install versions", ids objs = self.browse(cr, uid, ids) for o in objs: # download and unpack to destination folder url = 'http://www.tinyerp.org/download/modules_test/' + o.module.name + '_' + o.name + ".tar.gz" tar = tarfile.open(mode="r|gz", fileobj=urllib2.urlopen(url)) for tarinfo in tar: tar.extract(tarinfo, tools.config['addons_path']) return True def get_dependencies(self, cr, uid, ids, *args): dobj = self.pool.get('module.module.dependency') print "get_depends", ids # for each dependency, get dependencies objs = self.browse(cr, uid, ids) depends = [] for o in objs: print "name", o.name o_depends = dobj.resolve(cr, uid, [d.id for d in o.dependencies]) print "depends", o_depends depends.extend(o_depends) # depends.extend([d.id for d in o.dependencies]) print "total depends", depends # merge the list # return the list of ids return depends module_version() # a module dependency record represents one dependency of a particular version of a module # it can depend on a range of version of one module class module_dependency(osv.osv): _name = "module.module.dependency" _description = "Module dependency" _columns = { 'dependency_for': fields.many2one('module.module.version', 'Version'), 'module': fields.many2one('module.module', 'Module'), 'version_pattern': fields.char('Version pattern', size=128), } # returns the ids of module version records which match all dependencies # [version_id, ...] def resolve(self, cr, uid, ids): vobj = self.pool.get('module.module.version') objs = self.browse(cr, uid, ids) res = {} for o in objs: pattern = o.version_pattern and eval(o.version_pattern) or [] print "pattern", pattern 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()