diff options
Diffstat (limited to 'BitTorrent/NewVersion.py')
-rw-r--r-- | BitTorrent/NewVersion.py | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/BitTorrent/NewVersion.py b/BitTorrent/NewVersion.py new file mode 100644 index 0000000..1395370 --- /dev/null +++ b/BitTorrent/NewVersion.py @@ -0,0 +1,278 @@ +# The contents of this file are subject to the BitTorrent Open Source License +# Version 1.1 (the License). You may not copy or use this file, in either +# source code or executable form, except in compliance with the License. You +# may obtain a copy of the License at http://www.bittorrent.com/license/. +# +# Software distributed under the License is distributed on an AS IS basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. + +# written by Matt Chisholm + +import os +import sys +import zurllib +import pickle +import threading +from sha import sha + +DEBUG = False + +from BitTorrent import ERROR, WARNING, BTFailure, version, app_name +from BitTorrent import GetTorrent +from BitTorrent.bencode import bdecode, bencode +from BitTorrent.platform import os_version, spawn, get_temp_dir, doc_root, is_frozen_exe, osx +from BitTorrent.ConvertedMetainfo import ConvertedMetainfo + +if osx: + from Foundation import NSAutoreleasePool + +if is_frozen_exe or DEBUG: + # needed for py2exe to include the public key lib + from Crypto.PublicKey import DSA + +version_host = 'http://version.bittorrent.com/' +download_url = 'http://bittorrent.com/download.html' + +# based on Version() class from ShellTools package by Matt Chisholm, +# used with permission +class Version(list): + def __str__(self): + return '.'.join(map(str, self)) + + def is_beta(self): + return self[1] % 2 == 1 + + def from_str(self, text): + return Version( [int(t) for t in text.split('.')] ) + + def name(self): + if self.is_beta(): + return 'beta' + else: + return 'stable' + + from_str = classmethod(from_str) + +currentversion = Version.from_str(version) + +availableversion = None + +class Updater(object): + def __init__(self, threadwrap, newversionfunc, startfunc, installfunc, + errorfunc, test_new_version='', test_current_version=''): + self.threadwrap = threadwrap # for calling back to UI from thread + self.newversionfunc = newversionfunc # alert to new version UI function + self.startfunc = startfunc # start torrent UI function + self.installfunc = installfunc # install torrent UI function + self.errorfunc = errorfunc # report error UI function + self.infohash = None + self.version = currentversion + self.currentversion = currentversion + self.asked_for_install = False + self.version_site = version_host + if os.name == 'nt': + self.version_site += 'win32/' + if os_version not in ('XP', '2000', '2003'): + self.version_site += 'legacy/' + elif osx: + self.version_site += 'osx/' + self.debug_mode = DEBUG + if test_new_version: + test_new_version = Version.from_str(test_new_version) + self.debug_mode = True + def _hack_get_available(url): + return test_new_version + self._get_available = _hack_get_available + if test_current_version: + self.debug_mode = True + self.currentversion = Version.from_str(test_current_version) + + + def debug(self, message): + if self.debug_mode: + self.threadwrap(self.errorfunc, WARNING, message) + + + def _get_available(self, url): + self.debug('Updater.get_available() hitting url %s' % url) + try: + u = zurllib.urlopen(url) + s = u.read() + s = s.strip() + except: + raise BTFailure(_("Could not get latest version from %s")%url) + try: + assert len(s) == 5 + availableversion = Version.from_str(s) + except: + raise BTFailure(_("Could not parse new version string from %s")%url) + return availableversion + + + def get_available(self): + url = self.version_site + self.currentversion.name() + availableversion = self._get_available(url) + if availableversion.is_beta(): + if availableversion[1] != self.currentversion[1]: + availableversion = self.currentversion + if self.currentversion.is_beta(): + stable_url = self.version_site + 'stable' + available_stable_version = self._get_available(stable_url) + if available_stable_version > availableversion: + availableversion = available_stable_version + self.version = availableversion + self.debug('Updater.get_available() got %s' % str(self.version)) + return self.version + + + def get(self): + try: + self.get_available() + except BTFailure, e: + self.threadwrap(self.errorfunc, WARNING, e) + return + + if self.version <= self.currentversion: + self.debug('Updater.get() not updating old version %s' % str(self.version)) + return + + if not self.can_install(): + self.debug('Updater.get() cannot install on this os') + return + + self.installer_name = self.calc_installer_name() + self.installer_url = self.version_site + self.installer_name + '.torrent' + self.installer_dir = self.calc_installer_dir() + + self.torrentfile = None + torrentfile, terrors = GetTorrent.get_url(self.installer_url) + signature = None + try: + signfile = zurllib.urlopen(self.installer_url + '.sign') + except: + self.debug('Updater.get() failed to get signfile %s.sign' % self.installer_url) + else: + try: + signature = pickle.load(signfile) + except: + self.debug('Updater.get() failed to load signfile %s' % signfile) + + if terrors: + self.threadwrap(self.errorfunc, WARNING, '\n'.join(terrors)) + + if torrentfile and signature: + public_key_file = open(os.path.join(doc_root, 'public.key'), 'rb') + public_key = pickle.load(public_key_file) + h = sha(torrentfile).digest() + if public_key.verify(h, signature): + self.torrentfile = torrentfile + b = bdecode(torrentfile) + self.infohash = sha(bencode(b['info'])).digest() + self.total_size = b['info']['length'] + self.debug('Updater.get() got torrent file and signature') + else: + self.debug('Updater.get() torrent file signature failed to verify.') + pass + else: + self.debug('Updater.get() doesn\'t have torrentfile %s and signature %s' % + (str(type(torrentfile)), str(type(signature)))) + + def installer_path(self): + if self.installer_dir is not None: + return os.path.join(self.installer_dir, + self.installer_name) + else: + return None + + def check(self): + t = threading.Thread(target=self._check, + args=()) + t.setDaemon(True) + t.start() + + def _check(self): + if osx: + pool = NSAutoreleasePool.alloc().init() + self.get() + if self.version > self.currentversion: + self.threadwrap(self.newversionfunc, self.version, download_url) + + def can_install(self): + if self.debug_mode: + return True + if os.name == 'nt': + return True + elif osx: + return True + else: + return False + + def calc_installer_name(self): + if os.name == 'nt': + ext = 'exe' + elif osx: + ext = 'dmg' + elif os.name == 'posix' and self.debug_mode: + ext = 'tar.gz' + else: + return + + parts = [app_name, str(self.version)] + if self.version.is_beta(): + parts.append('Beta') + name = '-'.join(parts) + name += '.' + ext + return name + + def set_installer_dir(self, path): + self.installer_dir = path + + def calc_installer_dir(self): + if hasattr(self, 'installer_dir'): + return self.installer_dir + + temp_dir = get_temp_dir() + if temp_dir is not None: + return temp_dir + else: + self.errorfunc(WARNING, + _("Could not find a suitable temporary location to " + "save the %s %s installer.") % (app_name, self.version)) + + def installer_downloaded(self): + if self.installer_path() and os.access(self.installer_path(), os.F_OK): + size = os.stat(self.installer_path())[6] + if size == self.total_size: + return True + else: + #print 'installer is wrong size, is', size, 'should be', self.total_size + return False + else: + #print 'installer does not exist' + return False + + def download(self): + if self.torrentfile is not None: + self.startfunc(self.torrentfile, self.installer_path()) + else: + self.errorfunc(WARNING, _("No torrent file available for %s %s " + "installer.")%(app_name, self.version)) + + def start_install(self): + if not self.asked_for_install: + if self.installer_downloaded(): + self.asked_for_install = True + self.installfunc() + else: + self.errorfunc(WARNING, + _("%s %s installer appears to be incomplete, " + "missing, or corrupt.")%(app_name, + self.version)) + + def launch_installer(self, torrentqueue): + if os.name == 'nt': + spawn(torrentqueue, self.installer_path(), "/S") + else: + self.errorfunc(WARNING, _("Cannot launch installer on this OS")) |