def __unicode__(self): if hasattr(SafeException, '__unicode__'): # Python >= 2.6 if self.feed_url: return _('%s [%s]') % (SafeException.__unicode__(self), self.feed_url) return SafeException.__unicode__(self) else: return support.unicode(SafeException.__str__(self))
def __init__(self, message, ex = None): if ex: try: message += "\n\n(exact error: %s)" % ex except: # Some Python messages have type str but contain UTF-8 sequences. # (e.g. IOException). Adding these to a Unicode 'message' (e.g. # after gettext translation) will cause an error. import codecs decoder = codecs.lookup('utf-8') decex = decoder.decode(str(ex), errors = 'replace')[0] message += "\n\n(exact error: %s)" % decex SafeException.__init__(self, message)
def get_feed_targets(self, feed): """Return a list of Interfaces for which feed can be a feed. This is used by B{0install add-feed}. @param feed: the feed @type feed: L{model.ZeroInstallFeed} (or, deprecated, a URL) @rtype: [model.Interface] @raise SafeException: If there are no known feeds. @since: 0.53""" if not isinstance(feed, model.ZeroInstallFeed): # (deprecated) feed = self.get_feed(feed) if feed is None: raise SafeException( "Feed is not cached and using deprecated API") if not feed.feed_for: raise SafeException( _("Missing <feed-for> element in '%s'; " "it can't be used as a feed for any other interface.") % feed.url) feed_targets = feed.feed_for logger.debug(_("Feed targets: %s"), feed_targets) return [self.get_interface(uri) for uri in feed_targets]
def sign_xml(config, source_xml): child = subprocess.Popen(['gpg', '--detach-sign', '--default-key', config.GPG_SIGNING_KEY, '--use-agent', '--output', '-', '-'], stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE) stdout, stderr = child.communicate(source_xml) exit_status = child.wait() if exit_status: raise SafeException("Error signing feed: %s" % stderr) if stderr: print(stderr, file=sys.stderr) encoded = base64.encodestring(stdout) sig = "<!-- Base64 Signature\n" + encoded + "\n-->\n" return source_xml + sig
def get_size(url): print("Checking {url}... ".format(url=url), end='') try: scheme = urlparse.urlparse(url)[0].lower() if scheme.startswith('http') or scheme.startswith('https'): size = get_http_size(url) elif scheme.startswith('ftp'): size = get_ftp_size(url) else: raise SafeException("Unknown scheme '%s' in '%s'" % (scheme, url)) except: print("ERROR") raise print(size, "bytes") return size
def run(): keys_downloaded = tasks.Task( pending.download_keys(config.fetcher), "download keys") yield keys_downloaded.finished tasks.check(keys_downloaded.finished) if not config.iface_cache.update_feed_if_trusted( uri, pending.sigs, pending.new_xml): blocker = config.trust_mgr.confirm_keys(pending) if blocker: yield blocker tasks.check(blocker) if not config.iface_cache.update_feed_if_trusted( uri, pending.sigs, pending.new_xml): raise SafeException( _("No signing keys trusted; not importing"))
def extract_deb(stream, destdir, extract = None, start_offset = 0): if extract: raise SafeException(_('Sorry, but the "extract" attribute is not yet supported for Debs')) stream.seek(start_offset) # ar can't read from stdin, so make a copy... deb_copy_name = os.path.join(destdir, 'archive.deb') deb_copy = open(deb_copy_name, 'w') shutil.copyfileobj(stream, deb_copy) deb_copy.close() data_tar = None p = subprocess.Popen(('ar', 't', 'archive.deb'), stdout=subprocess.PIPE, cwd=destdir, universal_newlines=True) o = p.communicate()[0] for line in o.split('\n'): if line == 'data.tar': data_compression = None elif line == 'data.tar.gz': data_compression = 'gzip' elif line == 'data.tar.bz2': data_compression = 'bzip2' elif line == 'data.tar.lzma': data_compression = 'lzma' else: continue data_tar = line break else: raise SafeException(_("File is not a Debian package.")) _extract(stream, destdir, ('ar', 'x', 'archive.deb', data_tar)) os.unlink(deb_copy_name) data_name = os.path.join(destdir, data_tar) data_stream = open(data_name) os.unlink(data_name) extract_tar(data_stream, destdir, None, data_compression)
def lookup(uri, missing_ok = False): """Search repositories.json for the repository which hosts 'uri'.""" path = basedir.load_first_config('0install.net', '0repo', 'repositories.json') if path: with open(path, 'rb') as stream: db = json.load(stream) else: db = {} from_registry = None for key, value in list(db.items()): if uri.startswith(key): if from_registry: raise SafeException("Multiple matching repositories! {a} and {b}".format( a = from_registry, b = value)) from_registry = value if not from_registry: if missing_ok: return None else: raise SafeException("No registered repository for {uri} (hint: use '0repo register')".format(uri = uri)) return from_registry
def parse(value): v = float(value[:-1]) unit = value[-1] if unit == 's': return int(v) v *= 60 if unit == 'm': return int(v) v *= 60 if unit == 'h': return int(v) v *= 24 if unit == 'd': return int(v) raise SafeException(_('Unknown unit "%s" - use e.g. 5d for 5 days') % unit)
def handle(config, options, args): if len(args) == 2: iface = config.iface_cache.get_interface(model.canonical_iface_uri(args[0])) feed_url = args[1] feed_import = add_feed.find_feed_import(iface, feed_url) if not feed_import: raise SafeException(_('Interface %(interface)s has no feed %(feed)s') % {'interface': iface.uri, 'feed': feed_url}) iface.extra_feeds.remove(feed_import) writer.save_interface(iface) elif len(args) == 1: add_feed.handle(config, options, args, add_ok = False, remove_ok = True) else: raise UsageError()
def get_implementation(self, interface): """Get the chosen implementation. @type interface: Interface @rtype: L{model.Implementation} @raise SafeException: if interface has not been fetched or no implementation could be chosen.""" assert isinstance(interface, Interface) if not interface.name and not interface.feeds: raise SafeException(_("We don't have enough information to " "run this program yet. " "Need to download:\n%s") % interface.uri) try: return self.implementation[interface] except KeyError, ex: if interface.implementations: offline = "" if self.network_use == network_offline: raise SafeException(_("No usable implementation found for '%s'.\n" "This may be because 'Network Use' is set to Off-line.") % interface.name) raise SafeException(_("No usable implementation found for '%s'.") % interface.name) raise ex
def handle(config, options, args): if len(args) == 1: extract = None elif len(args) == 2: extract = args[1] else: raise UsageError() source = args[0] alg = manifest.algorithms.get(options.algorithm or 'sha1new', None) if alg is None: raise SafeException(_('Unknown algorithm "%s"') % alg) def do_manifest(d): if extract is not None: d = os.path.join(d, extract) digest = alg.new_digest() for line in alg.generate_manifest(d): digest.update(line + '\n') print(alg.getID(digest)) if os.path.isdir(source): if extract is not None: raise SafeException("Can't use extract with a directory") do_manifest(source) else: data = None tmpdir = tempfile.mkdtemp() try: data = open(args[0], 'rb') unpack.unpack_archive(source, data, tmpdir, extract) do_manifest(tmpdir) finally: support.ro_rmtree(tmpdir) if data: data.close()
def extract_cab(stream, destdir, extract, start_offset=0): "@since: 0.24" if extract: raise SafeException( _('Sorry, but the "extract" attribute is not yet supported for Cabinet files' )) stream.seek(start_offset) # cabextract can't read from stdin, so make a copy... cab_copy_name = os.path.join(destdir, 'archive.cab') cab_copy = file(cab_copy_name, 'w') shutil.copyfileobj(stream, cab_copy) cab_copy.close() _extract(stream, destdir, ['cabextract', '-s', '-q', 'archive.cab']) os.unlink(cab_copy_name)
def create_app(self, name, requirements): validate_name(name) apps_dir = basedir.save_config_path(namespaces.config_site, "apps") app_dir = os.path.join(apps_dir, name) if os.path.isdir(app_dir): raise SafeException( _("Application '{name}' already exists: {path}").format( name=name, path=app_dir)) os.mkdir(app_dir) app = App(self.config, app_dir) app.set_requirements(requirements) app.set_last_checked() return app
def handle_invoke(config, options, ticket, request): try: command = request[0] logger.debug("Got request '%s'", command) if command == 'get-selections-gui': response = do_get_selections_gui(config, request[1:]) elif command == 'wait-for-network': response = do_wait_for_network(config) elif command == 'download-selections': l = stdin.readline().strip() xml = qdom.parse(BytesIO(stdin.read(int(l)))) blocker = do_download_selections(config, options, request[1:], xml) reply_when_done(ticket, blocker) return #async elif command == 'get-package-impls': l = stdin.readline().strip() xml = qdom.parse(BytesIO(stdin.read(int(l)))) response = do_get_package_impls(config, options, request[1:], xml) elif command == 'is-distro-package-installed': l = stdin.readline().strip() xml = qdom.parse(BytesIO(stdin.read(int(l)))) response = do_is_distro_package_installed(config, options, xml) elif command == 'get-distro-candidates': l = stdin.readline().strip() xml = qdom.parse(BytesIO(stdin.read(int(l)))) blocker = do_get_distro_candidates(config, request[1:], xml) reply_when_done(ticket, blocker) return # async elif command == 'download-and-import-feed': blocker = do_download_and_import_feed(config, request[1:]) reply_when_done(ticket, blocker) return # async elif command == 'notify-user': response = do_notify_user(config, request[1]) else: raise SafeException("Internal error: unknown command '%s'" % command) response = ['ok', response] except SafeException as ex: logger.info("Replying with error: %s", ex) response = ['error', str(ex)] except Exception as ex: import traceback logger.info("Replying with error: %s", ex) response = ['error', traceback.format_exc().strip()] send_json(["return", ticket, response])
def do_gui(args): "gui [--no-prompt] [SOURCE-URI]" if args and args[0] == '--no-prompt': del args[0] # This option no longer has any effect, since it is the default. # However, old versions of 0launch's GUI pass it (< 0.52) from zeroinstall.gtkui import pygtkcompat pygtkcompat.enable() pygtkcompat.enable_gtk(version='3.0') import gui_support import gtk try: if len(args) == 0: pass elif len(args) == 1: import setup def get_dir_callback(default_dir): compile_dir = gui_support.choose_dir( _('Create build directory'), default_dir) if compile_dir: return compile_dir raise SafeException("Cancelled at user's request") setup.do_setup(args, get_dir_callback) else: raise SafeException("usage: 0compile gui URI") buildenv = BuildEnv() box = gui_support.CompileBox(buildenv.interface) box.connect('destroy', lambda b: gtk.main_quit()) box.show() gtk.main() except KeyboardInterrupt: pass except SafeException as ex: gui_support.alert(None, '%s' % ex) sys.exit(1) except Exception as ex: import traceback traceback.print_exc() gui_support.alert(None, '%s: %s' % (ex.__class__, ex)) sys.exit(1)
def recurse(sub): # To ensure that a line-by-line comparison of the manifests # is possible, we require that filenames don't contain newlines. # Otherwise, you can name a file so that the part after the \n # would be interpreted as another line in the manifest. if '\n' in sub: raise BadDigest("Newline in filename '%s'" % sub) assert sub.startswith('/') if sub == '/.manifest': return full = os.path.join(root, sub[1:].replace('/', os.sep)) info = os.lstat(full) m = info.st_mode if stat.S_ISDIR(m): if sub != '/': yield "D %s %s" % (int(info.st_mtime), sub) items = os.listdir(full) items.sort() subdir = sub if not subdir.endswith('/'): subdir += '/' for x in items: for y in recurse(subdir + x): yield y return assert sub[1:] leaf = os.path.basename(sub[1:]) if stat.S_ISREG(m): d = sha1_new(open(full).read()).hexdigest() if m & 0o111: yield "X %s %s %s %s" % (d, int( info.st_mtime), info.st_size, leaf) else: yield "F %s %s %s %s" % (d, int( info.st_mtime), info.st_size, leaf) elif stat.S_ISLNK(m): target = os.readlink(full) d = sha1_new(target).hexdigest() # Note: Can't use utime on symlinks, so skip mtime # Note: eCryptfs may report length as zero, so count ourselves instead yield "S %s %s %s" % (d, len(target), leaf) else: raise SafeException( _("Unknown object '%s' (not a file, directory or symlink)") % full)
def do_audit(args): """audit [DIRECTORY]""" if len(args) == 0: audit_stores = stores.stores else: audit_stores = [zerostore.Store(x) for x in args] audit_ls = [] total = 0 for a in audit_stores: if os.path.isdir(a.dir): items = sorted(os.listdir(a.dir)) audit_ls.append((a.dir, items)) total += len(items) elif len(args): raise SafeException(_("No such directory '%s'") % a.dir) verified = 0 failures = [] i = 0 for root, impls in audit_ls: print _("Scanning %s") % root for required_digest in impls: i += 1 path = os.path.join(root, required_digest) if '=' not in required_digest: print _("Skipping non-implementation directory %s") % path continue try: msg = _("[%(done)d / %(total)d] Verifying %(digest)s") % { 'done': i, 'total': total, 'digest': required_digest } print msg, sys.stdout.flush() verify(path, required_digest) print "\r" + (" " * len(msg)) + "\r", verified += 1 except zerostore.BadDigest, ex: print failures.append(path) print str(ex) if ex.detail: print print ex.detail
def solve_and_download_impls(self, refresh = False): """Run L{solve_with_downloads} and then get the selected implementations too. @raise SafeException: if we couldn't select a set of implementations @since: 0.40""" refreshed = self.solve_with_downloads(refresh) if refreshed: yield refreshed tasks.check(refreshed) if not self.solver.ready: raise SafeException(_("Can't find all required implementations:") + '\n' + '\n'.join(["- %s -> %s" % (iface, self.solver.selections[iface]) for iface in self.solver.selections])) downloaded = self.download_uncached_implementations() if downloaded: yield downloaded tasks.check(downloaded)
def ensure_no_uncommitted_changes(path): child = subprocess.Popen( ["git", "diff", "--exit-code", "HEAD", "--", abspath(path)], cwd=dirname(path), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, unused = child.communicate() if child.returncode == 0: return raise SafeException('Uncommitted changes in {feed}!\n' 'In the feeds directory, use:\n\n' '"git commit -a" to commit them, or\n' '"git stash" to discard.\n\n' 'Changes are:\n{changes}'.format(feed=path, changes=stdout))
def confirm_trust_keys(self, interface, sigs, iface_xml): """We don't trust any of the signatures yet. Ask the user. When done update the L{trust} database, and then call L{trust.TrustDB.notify}. @deprecated: see L{confirm_keys} @arg interface: the interface being updated @arg sigs: a list of signatures (from L{gpg.check_stream}) @arg iface_xml: the downloaded data (not yet trusted) @return: a blocker, if confirmation will happen asynchronously, or None @rtype: L{tasks.Blocker}""" import warnings warnings.warn(_("Use confirm_keys, not confirm_trust_keys"), DeprecationWarning, stacklevel=2) from zeroinstall.injector import trust, gpg assert sigs valid_sigs = [s for s in sigs if isinstance(s, gpg.ValidSig)] if not valid_sigs: raise SafeException( 'No valid signatures found on "%s". Signatures:%s' % (interface.uri, ''.join(['\n- ' + str(s) for s in sigs]))) domain = trust.domain_from_url(interface.uri) # Ask on stderr, because we may be writing XML to stdout print >> sys.stderr, "\nInterface:", interface.uri print >> sys.stderr, "The interface is correctly signed with the following keys:" for x in valid_sigs: print >> sys.stderr, "-", x if len(valid_sigs) == 1: print >> sys.stderr, "Do you want to trust this key to sign feeds from '%s'?" % domain else: print >> sys.stderr, "Do you want to trust all of these keys to sign feeds from '%s'?" % domain while True: print >> sys.stderr, "Trust [Y/N] ", i = raw_input() if not i: continue if i in 'Nn': raise NoTrustedKeys(_('Not signed with a trusted key')) if i in 'Yy': break for key in valid_sigs: print >> sys.stderr, "Trusting", key.fingerprint, "for", domain trust.trust_db.trust_key(key.fingerprint, domain) trust.trust_db.notify()
def parse_update_options(self, options): """Update the settings based on the options (used for "0install update APP"). @return: whether any settings were changed @rtype: bool @since: 1.9""" changed = False for key in ['not_before', 'before', 'message', 'cpu', 'os', 'command']: value = getattr(options, key) if value is not None: changed = changed or value != getattr(self, key) setattr(self, key, value) if options.source and not self.source: # (partly because it doesn't make much sense, and partly because you # can't undo it, as there's no --not-source option) from zeroinstall import SafeException raise SafeException("Can't update from binary to source type!") return changed
def recurse(sub): # To ensure that a line-by-line comparison of the manifests # is possible, we require that filenames don't contain newlines. # Otherwise, you can name a file so that the part after the \n # would be interpreted as another line in the manifest. if '\n' in sub: raise BadDigest(_("Newline in filename '%s'") % sub) assert sub.startswith('/') full = os.path.join(root, sub[1:]) info = os.lstat(full) new_digest = self.new_digest m = info.st_mode if not stat.S_ISDIR(m): raise Exception(_('Not a directory: "%s"') % full) if sub != '/': yield "D %s" % sub items = os.listdir(full) items.sort() dirs = [] for leaf in items: path = os.path.join(root, sub[1:], leaf) info = os.lstat(path) m = info.st_mode if stat.S_ISREG(m): if leaf == '.manifest': continue d = new_digest(file(path).read()).hexdigest() if m & 0111: yield "X %s %s %s %s" % (d, int(info.st_mtime), info.st_size, leaf) else: yield "F %s %s %s %s" % (d, int(info.st_mtime), info.st_size, leaf) elif stat.S_ISLNK(m): target = os.readlink(path) d = new_digest(target).hexdigest() # Note: Can't use utime on symlinks, so skip mtime # Note: eCryptfs may report length as zero, so count ourselves instead yield "S %s %s %s" % (d, len(target), leaf) elif stat.S_ISDIR(m): dirs.append(leaf) else: raise SafeException(_("Unknown object '%s' (not a file, directory or symlink)") % path) for x in dirs: for y in recurse(os.path.join(sub, x)): yield y return
def extract_dmg(stream, destdir, extract, start_offset = 0): "@since: 0.46" if extract: raise SafeException(_('Sorry, but the "extract" attribute is not yet supported for DMGs')) stream.seek(start_offset) # hdiutil can't read from stdin, so make a copy... dmg_copy_name = os.path.join(destdir, 'archive.dmg') dmg_copy = open(dmg_copy_name, 'w') shutil.copyfileobj(stream, dmg_copy) dmg_copy.close() mountpoint = mkdtemp(prefix='archive') subprocess.check_call(["hdiutil", "attach", "-quiet", "-mountpoint", mountpoint, "-nobrowse", dmg_copy_name]) subprocess.check_call(["cp", "-pR"] + glob.glob("%s/*" % mountpoint) + [destdir]) subprocess.check_call(["hdiutil", "detach", "-quiet", mountpoint]) os.rmdir(mountpoint) os.unlink(dmg_copy_name)
def _extract(stream, destdir, command, start_offset = 0): """Run execvp('command') inside destdir in a child process, with stream seeked to 'start_offset' as stdin.""" # Some zip archives are missing timezone information; force consistent results child_env = os.environ.copy() child_env['TZ'] = 'GMT' stream.seek(start_offset) # TODO: use pola-run if available, once it supports fchmod child = subprocess.Popen(command, cwd = destdir, stdin = stream, stderr = subprocess.PIPE, env = child_env) unused, cerr = child.communicate() status = child.wait() if status != 0: raise SafeException(_('Failed to extract archive (using %(command)s); exit code %(status)d:\n%(err)s') % {'command': command, 'status': status, 'err': cerr.strip()})
def __init__(self, need_config = True): if need_config and not os.path.isfile(ENV_FILE): raise SafeException("Run 0compile from a directory containing a '%s' file" % ENV_FILE) self.config = ConfigParser.RawConfigParser() self.config.add_section('compile') self.config.set('compile', 'download-base-url', '') self.config.set('compile', 'version-modifier', '') self.config.set('compile', 'interface', '') self.config.set('compile', 'selections', '') self.config.set('compile', 'metadir', '0install') self.config.set('compile', 'distdir', '') self.config.read(ENV_FILE) self._selections = None return
def find_config(missing_ok=False): """Change to parent directory until we find one with 0repo-config.py.""" def is_root_dir(): if os.name == 'nt': # Top-level directories on Windows are always three characters long (e.g. 'C:\') return len(os.getcwd()) == 3 else: return os.path.samefile('.', '..') # Walk up the directory tree to find the root of the repository while not os.path.isfile('0repo-config.py'): if is_root_dir(): if missing_ok: return False raise SafeException( '0repo must be run from a repository directory (a directory that contains\n' 'a "0repo-config.py" file). To create a new repository, use "0repo create"' ) os.chdir('..') return True
def extract_zip(stream, destdir, extract, start_offset=0): if extract: # Limit the characters we accept, to avoid sending dodgy # strings to zip if not re.match('^[a-zA-Z0-9][- _a-zA-Z0-9.]*$', extract): raise SafeException(_('Illegal character in extract attribute')) stream.seek(start_offset) # unzip can't read from stdin, so make a copy... zip_copy_name = os.path.join(destdir, 'archive.zip') with open(zip_copy_name, 'wb') as zip_copy: shutil.copyfileobj(stream, zip_copy) args = ['unzip', '-q', '-o', 'archive.zip'] if extract: args.append(extract + '/*') _extract(stream, destdir, args) os.unlink(zip_copy_name)
def expand_impl_relative_urls(config, parent, impl): for elem in parent.childNodes: if elem.nodeType != Node.ELEMENT_NODE: continue if elem.namespaceURI != XMLNS_IFACE: continue if elem.localName in ('archive', 'file'): archive = elem.getAttribute('href') assert archive if '/' not in archive: x = config.archive_db.lookup(archive) if not x and os.path.exists(os.path.join('incoming', archive)): x = import_missing_archive(config, impl, archive) if not x: raise SafeException("Missing entry for {basename} in {db}; can't build feeds." "Place missing archives in 'incoming' and try again.".format( basename = archive, db = config.archive_db.path)) elem.setAttribute('href', x.url) elif elem.localName == 'recipe': expand_impl_relative_urls(config, elem, impl = impl)
def spawn_build(self, iface_name): assert self.child is None self.details.insert_at_end_and_scroll('Building %s\n' % iface_name, 'heading') # Group all the child processes so we can kill them easily def become_group_leader(): os.setpgid(0, 0) devnull = os.open(os.devnull, os.O_RDONLY) try: self.child = subprocess.Popen( [sys.executable, '-u', sys.argv[0], 'build'], stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, preexec_fn=become_group_leader) finally: os.close(devnull) import codecs decoder = codecs.getincrementaldecoder('utf-8')(errors='replace') while True: yield tasks.InputBlocker(self.child.stdout, 'output from child') got = os.read(self.child.stdout.fileno(), 100) chars = decoder.decode(got, final=not got) self.details.insert_at_end_and_scroll(chars) if not got: break self.child.wait() code = self.child.returncode self.child = None if code: self.details.insert_at_end_and_scroll( 'Build process exited with error status %d\n' % code, 'error') raise SafeException('Build process exited with error status %d' % code) self.details.insert_at_end_and_scroll('Build completed successfully\n', 'heading')
def add_to_menu(feed, icon_path, category, zlaunch=None): """Write a .desktop file for this application. @param feed: the master feed of the program being added @param icon_path: the path of the icon, or None @param category: the freedesktop.org menu category""" if isinstance(feed, model.Interface): import warnings warnings.warn("API change: pass a ZeroInstallFeed, not an Interface", DeprecationWarning, 2) iface_uri = feed.uri else: iface_uri = feed.url tmpdir = tempfile.mkdtemp(prefix='zero2desktop-') try: desktop_name = os.path.join( tmpdir, 'zeroinstall-%s.desktop' % feed.get_name().lower().replace(os.sep, '-').replace(' ', '')) desktop = open(desktop_name, 'w') desktop.write( _template % { 'name': feed.get_name(), 'comment': feed.summary, '0launch': zlaunch or '0launch', 'iface': iface_uri, 'category': category }) if icon_path: desktop.write(_icon_template % icon_path) if len(feed.get_metadata(namespaces.XMLNS_IFACE, 'needs-terminal')): desktop.write('Terminal=true\n') desktop.close() status = os.spawnlp(os.P_WAIT, 'xdg-desktop-menu', 'xdg-desktop-menu', 'install', desktop_name) finally: shutil.rmtree(tmpdir) if status: raise SafeException( _('Failed to run xdg-desktop-menu (error code %d)') % status)
def suggest_release_version(snapshot_version): """Given a snapshot version, suggest a suitable release version. >>> suggest_release_version('1.0-pre') '1.0' >>> suggest_release_version('0.9-post') '0.10' >>> suggest_release_version('3') Traceback (most recent call last): ... SafeException: Version '3' is not a snapshot version (should end in -pre or -post) """ version = model.parse_version(snapshot_version) mod = version[-1] if mod == 0: raise SafeException( "Version '%s' is not a snapshot version (should end in -pre or -post)" % snapshot_version) if mod > 0: # -post, so increment the number version[-2][-1] += 1 version[-1] = 0 # Remove the modifier return model.format_version(version)
def __init__(self, message = None): SafeException.__init__(self, message or _("Download aborted at user's request"))
def __str__(self): if self.feed_url: return SafeException.__str__(self) + ' in ' + self.feed_url return SafeException.__str__(self)