root/branch/PacketManipulator/PM/Gui/Plugins/Update.py @ 3945

Revision 3945, 10.4 kB (checked in by nopper, 4 years ago)

General fixing

Line 
1# -*- coding: utf-8 -*-
2# Copyright (C) 2008 Adriano Monteiro Marques
3#
4# Author: Francesco Piccinno <stack.box@gmail.com>
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
20import os
21
22try:
23    from hashlib import md5
24except ImportError:
25    from md5 import md5
26
27from threading import RLock
28from tempfile import mkstemp
29from xml.dom.minidom import parseString
30
31from PM.Core.I18N import _
32from PM.Core.Logger import log
33from PM.Core.Const import PM_PLUGINS_TEMP_DIR
34
35from PM.Gui.Plugins.Network import *
36from PM.Gui.Plugins.Atoms import Version
37
38STATUS_IDLE = 0
39
40LATEST_ERROR = 1
41LATEST_GETTED = 2
42LATEST_GETTING = 3
43
44FILE_ERROR = 4
45FILE_GETTED = 5
46FILE_GETTING = 6
47FILE_CHECKING = 7
48
49class UpdateObject(object):
50    def __init__(self, obj):
51        self.buffer = []
52       
53        self.status = STATUS_IDLE
54       
55        self.label = None
56        self.fract = None
57       
58        self.url = None
59        self.version = None
60        self.hash = None
61       
62        self.object = obj
63       
64        self.fd = None
65       
66        self.size = None
67        self.total = None
68
69        # Simple lock for sync
70        self.lock = RLock()
71
72    def parse_latest_file(self):
73        """
74        @return url, version or None, None on error
75        """
76
77        try:
78            doc = parseString("".join(self.buffer))
79           
80            if doc.documentElement.tagName != 'UmitPluginUpdate':
81                raise Exception("Not valid xml file.")
82
83            url, version, hash = None, None, None
84
85            for node in doc.documentElement.childNodes:
86                if node.nodeName == 'update-uri':
87                    url = node.firstChild.data
88                if node.nodeName == 'version':
89                    version = node.firstChild.data
90                if node.nodeName == 'md5':
91                    hash = node.firstChild.data
92
93            return url, version, hash
94        except Exception, exc:
95            log.warning("__parse_xml: %s" % exc)
96            return None, None, None
97
98
99class UpdateEngine(object):
100    """
101    A class that permits the updating stuff
102    """
103
104    def __init__(self):
105        "Initialize an UpdateEngine"
106        self.update_lst = None
107        self.static_lst = None
108        self.updating = False
109   
110    def stop(self):
111        "Mark as stopped"
112        self.updating = False
113
114    def start_update(self):
115        """
116        Start the update phase (getting the latest.xml files).
117        You have to set the list first.
118        Do nothing if already started.
119
120        Remember to call stop on finish.
121        """
122
123        if not self.updating:
124
125            for obj in self.update_lst:
126                obj.status = LATEST_GETTING
127
128            self.__process_next()
129       
130        self.updating = True
131   
132    def start_download(self):
133        """
134        Start the download phas (getting the real files)
135        You have to set the list first.
136        Do nothing if already started.
137
138        Remember to call stop on finish.
139        """
140
141        if not self.updating:
142
143            for obj in self.update_lst:
144                obj.status = FILE_GETTING
145
146            self.__process_next_download()
147           
148        self.updating = True
149   
150    def __process_next_download(self):
151        """
152        Process the next avaiable download in the list
153        """
154
155        if not self.update_lst:
156            return
157       
158        obj = self.update_lst.pop(0)
159       
160        try:
161            filename = os.path.basename(obj.object.reader.get_path())
162           
163            obj.fd = open(mkstemp(".part", filename, \
164                          PM_PLUGINS_TEMP_DIR)[1], "wb+")
165                         
166            Network.get_url(obj.url, self.__process_plugin, obj)
167        except Exception, err:
168            obj.status = FILE_ERROR
169            obj.label = err
170            obj.fract = None
171           
172            self.__process_next_download()
173   
174    def __process_plugin(self, file, data, exc, obj):
175        """
176        Process callback for plugin data
177        """
178
179        if isinstance(exc, ErrorNetException):
180            obj.lock.acquire()
181
182            try:
183                obj.status = FILE_ERROR
184                obj.label = exc.reason
185                obj.fract = 1
186               
187                self.__process_next_download()
188                return
189            finally:
190                obj.lock.release()
191       
192        elif isinstance(exc, StopNetException):
193            if obj.hash:
194
195                data = ""
196                obj.lock.acquire()
197
198                try:
199                    obj.label = _('Checking validity ...')
200                    obj.status = FILE_CHECKING
201               
202                    obj.fd.flush()
203                    obj.fd.seek(0)
204
205                    data = obj.fd.read()
206                finally:
207                    obj.lock.release()
208               
209                # Not locked it could freeze the ui
210                hasher = md5(data)
211               
212                obj.lock.acquire()
213
214                try:
215                    if hasher.hexdigest() == obj.hash:
216                        obj.label = _('Updated. Restart to take effect')
217                        obj.status = FILE_GETTED
218                    else:
219                        obj.label = _('Corrupted file.')
220                        obj.status = FILE_ERROR
221                finally:
222                    obj.lock.release()
223
224            else:
225                obj.lock.acquire()
226
227                try:
228                    obj.label = _('Updated. Restart to take effect')
229                    obj.status = FILE_GETTED
230                finally:
231                    obj.lock.release()
232           
233            obj.lock.acquire()
234
235            try:
236                obj.fd.close()
237                obj.fract = 1
238            finally:
239                obj.lock.release()
240           
241            try:
242                if obj.status == FILE_ERROR:
243                    os.remove(obj.fd.name)
244                else:
245                    os.rename(obj.fd.name, \
246                              obj.fd.name[:obj.fd.name.index(".ump") + 4])
247            except Exception:
248                # TODO: add more sensed control?
249                pass
250           
251            self.__process_next()
252       
253        elif isinstance(exc, StartNetException):
254            obj.lock.acquire()
255
256            try:
257                try:
258                    obj.status = FILE_GETTING
259                    obj.size = 0
260                    obj.total = int(file.info()['Content-Length'])
261                except:
262                    pass
263           
264                obj.label = _('Downloading ...')
265            finally:
266                obj.lock.release()
267
268        elif not exc:
269            if obj.total:
270                obj.size += len(data)
271                obj.fract = float(obj.size) / float(obj.total)
272            obj.fd.write(data)
273   
274    def __process_next(self):
275        """
276        Forward to the next update (download the next latest.xml)
277        """
278
279        if not self.update_lst:
280            return
281       
282        obj = self.update_lst.pop(0)
283        Network.get_url("%s/latest.xml" % obj.object.reader.update, \
284                        self.__process_manifest, obj)
285
286    def __process_manifest(self, file, data, exc, obj):
287        """
288        Callback to parse latest.xml file containing meta information about
289        the avaiable update.
290        """
291
292        if isinstance(exc, ErrorNetException):
293            obj.lock.acquire()
294
295            try:
296                obj.status = LATEST_ERROR
297                obj.label = _('Cannot find newer version (%s)') % exc.reason
298               
299                self.__process_next()
300                return
301            finally:
302                obj.lock.release()
303       
304        elif isinstance(exc, StopNetException):
305            url, version, hash = obj.parse_latest_file()
306
307            new_v = Version(version)
308            cur_v = Version(obj.object.reader.version)
309
310            type = -1 # -1 no action / 0 update / 1 downgrade
311
312            if new_v > cur_v:
313                type = 0
314            elif cur_v < new_v:
315                type = 1
316
317            obj.lock.acquire()
318
319            try:
320                if url and version and type >= 0:
321
322                    # We check if the path is the plugins in config_dir
323
324                    plug_dir = os.path.join(Path.config_dir, 'plugins')
325
326                    if os.path.dirname(obj.object.reader.get_path()) != plug_dir and \
327                       os.access(plug_dir, os.O_RDWR):
328
329                        obj.status = LATEST_ERROR
330
331                        if not type:
332                            obj.label = _('Version %s avaiable but need manual update.') % version
333                        else:
334                            obj.label = _('Version %s avaiable but need manual downgrade.') % version
335                    else:
336                        obj.status = LATEST_GETTED
337                        obj.label = _('Version %s avaiable.') % version
338                   
339                    obj.version = version
340                    obj.url = url
341                    obj.hash = hash
342                else:
343                    obj.status = LATEST_ERROR
344
345                    if type < 0:
346                        obj.label = _('Unable to parse latest.xml')
347                    else:
348                        obj.label = _('No applicable updates found')
349
350                self.__process_next()
351            finally:
352                obj.lock.release()
353
354        elif not exc:
355            obj.buffer.append(data)
356   
357    def get_list(self):
358        "Getter for list"
359        return self.static_lst
360   
361    def set_list(self, value):
362        "Setter for list"
363        lst = []
364       
365        for iter in value:
366            if isinstance(iter, UpdateObject):
367                lst.append(iter)
368            else:
369                lst.append(UpdateObject(iter))
370               
371        self.update_lst = lst
372        self.static_lst = tuple(self.update_lst)
373       
374    list = property(get_list, set_list)
Note: See TracBrowser for help on using the browser.