def create(self, remove_all_rc_files=False): """ Create default configuration files at either the default location or the given directory. """ # Check and create configuration directory if os.path.exists(self.config_dir): self.LOG.debug("Configuration directory %r already exists!" % (self.config_dir, )) else: os.mkdir(self.config_dir) if remove_all_rc_files: for subdir in ('.', 'rtorrent.d'): config_files = list( glob.glob( os.path.join(os.path.abspath(self.config_dir), subdir, '*.rc'))) config_files += list( glob.glob( os.path.join(os.path.abspath(self.config_dir), subdir, '*.rc.default'))) for config_file in config_files: self.LOG.info("Removing %r!" % (config_file, )) os.remove(config_file) # Create default configuration files for filepath in sorted(walk_resources("pyrocore", "data/config")): # Load from package data text = pymagic.resource_string("pyrocore", "data/config" + filepath) # Create missing subdirs config_file = self.config_dir + filepath if not os.path.exists(os.path.dirname(config_file)): os.makedirs(os.path.dirname(config_file)) # Write configuration files config_trail = [".default"] if os.path.exists(config_file): self.LOG.debug("Configuration file %r already exists!" % (config_file, )) else: config_trail.append('') for i in config_trail: with open(config_file + i, "w") as handle: handle.write(text) self.LOG.info("Configuration file %r written!" % (config_file + i, ))
) ##print "---", residue - ignorable if residue and residue != ignorable: self._engine.LOG.info("Keeping non-empty directory '%s' with %d %s%s!" % ( path, len(residue), "entry" if len(residue) == 1 else "entries", (" (%d ignorable)" % len(ignorable)) if ignorable else "", )) else: ##print "---", ignorable for waif in ignorable:# - doomed: waif = os.path.join(path, waif) self._engine.LOG.debug("Deleting waif '%s'" % (waif,)) if not dry_run: try: os.remove(waif) except EnvironmentError, exc: self._engine.LOG.warn("Problem deleting waif '%s' (%s)" % (waif, exc)) ##self._engine.LOG.debug("Deleting empty directory '%s'" % (path,)) doomed.update(remove_with_links(path)) # Delete item from engine if not dry_run: self.delete() def flush(self): """ Write volatile data to disk. """ self._make_it_so("saving session data of", ["save_session"])
else: self.LOG.info("Changing %r..." % filename) if not self.options.dry_run: # Write to temporary file tempname = os.path.join( os.path.dirname(filename), '.' + os.path.basename(filename), ) self.LOG.debug("Writing %r..." % tempname) bencode.bwrite(tempname, metainfo) # Replace existing file if os.name != "posix": # cannot rename to existing target on WIN32 os.remove(filename) try: os.rename(tempname, filename) except EnvironmentError, exc: # TODO: Try to write directly, keeping a backup! raise error.LoggableError("Can't rename tempfile %r to %r (%s)" % ( tempname, filename, exc )) changed += 1 # Print summary if changed: self.LOG.info("%s %d metafile(s)." % ( "Would've changed" if self.options.dry_run else "Changed", changed
def mainloop(self): """ The main loop. """ if not self.args: self.parser.error("No metafiles given, nothing to do!") if 1 < sum(bool(i) for i in (self.options.no_ssl, self.options.reannounce, self.options.reannounce_all)): self.parser.error("Conflicting options --no-ssl, --reannounce and --reannounce-all!") # Set filter criteria for metafiles filter_url_prefix = None if self.options.reannounce: # <scheme>://<netloc>/<path>?<query> filter_url_prefix = urlparse.urlsplit(self.options.reannounce, allow_fragments=False) filter_url_prefix = urlparse.urlunsplit(( filter_url_prefix.scheme, filter_url_prefix.netloc, '/', '', '' # bogus pylint: disable=E1103 )) self.LOG.info("Filtering for metafiles with announce URL prefix %r..." % filter_url_prefix) if self.options.reannounce_all: self.options.reannounce = self.options.reannounce_all else: # When changing the announce URL w/o changing the domain, don't change the info hash! self.options.no_cross_seed = True # Resolve tracker alias, if URL doesn't look like an URL if self.options.reannounce and not urlparse.urlparse(self.options.reannounce).scheme: tracker_alias, idx = self.options.reannounce, "0" if '.' in tracker_alias: tracker_alias, idx = tracker_alias.split('.', 1) try: idx = int(idx, 10) _, tracker_url = config.lookup_announce_alias(tracker_alias) self.options.reannounce = tracker_url[idx] except (KeyError, IndexError, TypeError, ValueError) as exc: raise error.UserError("Unknown tracker alias or bogus URL %r (%s)!" % ( self.options.reannounce, exc)) # go through given files bad = 0 changed = 0 for filename in self.args: try: # Read and remember current content metainfo = bencode.bread(filename) old_metainfo = bencode.bencode(metainfo) except (EnvironmentError, KeyError, bencode.BencodeError) as exc: self.LOG.warning("Skipping bad metafile %r (%s: %s)" % (filename, type(exc).__name__, exc)) bad += 1 else: # Check metafile integrity try: metafile.check_meta(metainfo) except ValueError as exc: self.LOG.warn("Metafile %r failed integrity check: %s" % (filename, exc,)) if not self.options.no_skip: continue # Skip any metafiles that don't meet the pre-conditions if filter_url_prefix and not metainfo['announce'].startswith(filter_url_prefix): self.LOG.warn("Skipping metafile %r no tracked by %r!" % (filename, filter_url_prefix,)) continue # Keep resume info safe libtorrent_resume = {} if "libtorrent_resume" in metainfo: try: libtorrent_resume["bitfield"] = metainfo["libtorrent_resume"]["bitfield"] except KeyError: pass # nothing to remember libtorrent_resume["files"] = copy.deepcopy(metainfo["libtorrent_resume"]["files"]) # Change private flag? if self.options.make_private and not metainfo["info"].get("private", 0): self.LOG.info("Setting private flag...") metainfo["info"]["private"] = 1 if self.options.make_public and metainfo["info"].get("private", 0): self.LOG.info("Clearing private flag...") del metainfo["info"]["private"] # Remove non-standard keys? if self.options.clean or self.options.clean_all or self.options.clean_xseed: metafile.clean_meta(metainfo, including_info=not self.options.clean, logger=self.LOG.info) # Restore resume info? if self.options.clean_xseed: if libtorrent_resume: self.LOG.info("Restoring key 'libtorrent_resume'...") metainfo.setdefault("libtorrent_resume", {}) metainfo["libtorrent_resume"].update(libtorrent_resume) else: self.LOG.warn("No resume information found!") # Clean rTorrent data? if self.options.clean_rtorrent: for key in self.RT_RESUMT_KEYS: if key in metainfo: self.LOG.info("Removing key %r..." % (key,)) del metainfo[key] # Change announce URL? if self.options.reannounce: metainfo['announce'] = self.options.reannounce if "announce-list" in metainfo: del metainfo["announce-list"] if not self.options.no_cross_seed: # Enforce unique hash per tracker metainfo["info"]["x_cross_seed"] = hashlib.md5(self.options.reannounce).hexdigest() if self.options.no_ssl: # We're assuming here the same (default) port is used metainfo['announce'] = (metainfo['announce'] .replace("https://", "http://").replace(":443/", ":80/")) # Change comment or creation date? if self.options.comment is not None: if self.options.comment: metainfo["comment"] = self.options.comment elif "comment" in metainfo: del metainfo["comment"] if self.options.bump_date: metainfo["creation date"] = int(time.time()) if self.options.no_date and "creation date" in metainfo: del metainfo["creation date"] # Add fast-resume data? if self.options.hashed: try: metafile.add_fast_resume(metainfo, self.options.hashed.replace("{}", metainfo["info"]["name"])) except EnvironmentError as exc: self.fatal("Error making fast-resume data (%s)" % (exc,)) raise # Set specific keys? metafile.assign_fields(metainfo, self.options.set, self.options.debug) replace_fields(metainfo, self.options.regex) # Write new metafile, if changed new_metainfo = bencode.bencode(metainfo) if new_metainfo != old_metainfo: if self.options.output_directory: filename = os.path.join(self.options.output_directory, os.path.basename(filename)) self.LOG.info("Writing %r..." % filename) if not self.options.dry_run: bencode.bwrite(filename, metainfo) if "libtorrent_resume" in metainfo: # Also write clean version filename = filename.replace(".torrent", "-no-resume.torrent") del metainfo["libtorrent_resume"] self.LOG.info("Writing %r..." % filename) bencode.bwrite(filename, metainfo) else: self.LOG.info("Changing %r..." % filename) if not self.options.dry_run: # Write to temporary file tempname = os.path.join( os.path.dirname(filename), '.' + os.path.basename(filename), ) self.LOG.debug("Writing %r..." % tempname) bencode.bwrite(tempname, metainfo) # Replace existing file if os.name != "posix": # cannot rename to existing target on WIN32 os.remove(filename) try: os.rename(tempname, filename) except EnvironmentError as exc: # TODO: Try to write directly, keeping a backup! raise error.LoggableError("Can't rename tempfile %r to %r (%s)" % ( tempname, filename, exc )) changed += 1 # Print summary if changed: self.LOG.info("%s %d metafile(s)." % ( "Would've changed" if self.options.dry_run else "Changed", changed )) if bad: self.LOG.warn("Skipped %d bad metafile(s)!" % (bad))
def mainloop(self): """ Handle theme selection changes, or rotate through selection. """ config_dir = self.options.config_dir or os.path.expanduser("~/.pyroscope") themes_dir = os.path.join(config_dir, 'color-schemes') selected_file = os.path.join(themes_dir, '.selected') current_file = os.path.join(themes_dir, '.current') # Read persisted state selected_themes = [] if os.path.exists(selected_file): with open(selected_file, 'rt') as handle: selected_themes = [x.strip() for x in handle] current_theme = None if os.path.exists(current_file): with open(current_file, 'rt') as handle: current_theme = handle.readline().strip() # Scan config for available themes themes = {} for ext in ('.rc.default', '.rc'): for filepath in glob.glob(themes_dir + '/*' + ext): name = os.path.basename(filepath).split('.')[0] if name: # This prefers '.rc.default' files, because those are checked first themes.setdefault(name, filepath) # Use available selected themes in given order, if there are any, else all themes if selected_themes and not set(selected_themes).isdisjoint(set(themes)): theme_list = [x for x in selected_themes if x in themes] else: theme_list = list(sorted(themes)) # Check options if self.options.list or self.options.all or self.options.toggle: if self.options.all: if os.path.exists(selected_file): os.remove(selected_file) selected_themes = [] for name in (self.options.toggle or '').replace(',', ' ').split(): if name not in themes: self.parser.error("Unknown theme {0!r}, use '--list' to show them".format(name)) elif name in selected_themes: selected_themes = [x for x in selected_themes if x != name] else: selected_themes.append(name) with open(selected_file, 'wt') as handle: handle.write('\n'.join(selected_themes + [''])) if self.options.list: for name in sorted(themes): print("{} {} {}".format( '*' if name == current_theme else ' ', '{:2d}'.format(selected_themes.index(name) + 1) if name in selected_themes else ' ', name, )) elif self.options.current or self.options.next: # Determine current theme, or rotate to next one new_theme = theme_list[0] if self.options.current and current_theme in theme_list: new_theme = current_theme elif current_theme in theme_list: new_theme = (theme_list * 2)[theme_list.index(current_theme) + 1] # Persist new theme if new_theme != current_theme: with open(current_file, 'wt') as handle: handle.write(new_theme + '\n') # Return result sys.stdout.write(themes[new_theme]) sys.stdout.flush() else: self.LOG.info("Current color theme is '{}'.".format( current_theme if current_theme in theme_list else theme_list[0])) self.LOG.info("Use '--help' to get usage information.")
def cull(self, file_filter=None, attrs=None): """ Delete ALL data files and remove torrent from client. @param file_filter: Optional callable for selecting a subset of all files. The callable gets a file item as described for RtorrentItem._get_files and must return True for items eligible for deletion. @param attrs: Optional list of additional attributes to fetch for a filter. """ dry_run = 0 # set to 1 for testing def remove_with_links(path): "Remove a path including any symlink chains leading to it." rm_paths = [] while os.path.islink(path): target = os.readlink(path) rm_paths.append(path) path = target if os.path.exists(path): rm_paths.append(path) else: self._engine.LOG.debug( "Real path '%s' doesn't exist," " but %d symlink(s) leading to it will be deleted..." % (path, len(rm_paths))) # Remove the link chain, starting at the real path # (this prevents losing the chain when there's permission problems) for rm_path in reversed(rm_paths): is_dir = os.path.isdir(rm_path) and not os.path.islink(rm_path) self._engine.LOG.debug("Deleting '%s%s'" % (rm_path, '/' if is_dir else '')) if not dry_run: try: (os.rmdir if is_dir else os.remove)(rm_path) except OSError as exc: if exc.errno == errno.ENOENT: # Seems this disappeared somehow inbetween (race condition) self._engine.LOG.info( "Path '%s%s' disappeared before it could be deleted" % (rm_path, '/' if is_dir else '')) else: raise return rm_paths # Assemble doomed files and directories files, dirs = set(), set() base_path = os.path.expanduser(self.directory) item_files = list(self._get_files(attrs=attrs)) if not self.directory: raise error.EngineError( "Directory for item #%s is empty," " you might want to add a filter 'directory=!'" % (self._fields["hash"], )) if not os.path.isabs(base_path): raise error.EngineError( "Directory '%s' for item #%s is not absolute, which is a bad idea;" " fix your .rtorrent.rc, and use 'directory.default.set = /...'" % ( self.directory, self._fields["hash"], )) if self.fetch("=is_multi_file") and os.path.isdir(self.directory): dirs.add(self.directory) for item_file in item_files: if file_filter and not file_filter(item_file): continue #print repr(item_file) path = os.path.join(base_path, item_file.path) files.add(path) if '/' in item_file.path: dirs.add(os.path.dirname(path)) # Delete selected files if not dry_run: self.stop() for path in sorted(files): ##self._engine.LOG.debug("Deleting file '%s'" % (path,)) remove_with_links(path) # Prune empty directories (longer paths first) doomed = files | dirs for path in sorted(dirs, reverse=True): residue = set(os.listdir(path) if os.path.exists(path) else []) ignorable = set(i for i in residue if any( fnmatch.fnmatch(i, pat) for pat in config.waif_pattern_list) #or os.path.join(path, i) in doomed ) ##print "---", residue - ignorable if residue and residue != ignorable: self._engine.LOG.info( "Keeping non-empty directory '%s' with %d %s%s!" % ( path, len(residue), "entry" if len(residue) == 1 else "entries", (" (%d ignorable)" % len(ignorable)) if ignorable else "", )) else: ##print "---", ignorable for waif in ignorable: # - doomed: waif = os.path.join(path, waif) self._engine.LOG.debug("Deleting waif '%s'" % (waif, )) if not dry_run: try: os.remove(waif) except EnvironmentError as exc: self._engine.LOG.warn( "Problem deleting waif '%s' (%s)" % (waif, exc)) ##self._engine.LOG.debug("Deleting empty directory '%s'" % (path,)) doomed.update(remove_with_links(path)) # Delete item from engine if not dry_run: self.delete()
self._engine.LOG.info( "Keeping non-empty directory '%s' with %d %s%s!" % ( path, len(residue), "entry" if len(residue) == 1 else "entries", (" (%d ignorable)" % len(ignorable)) if ignorable else "", )) else: ##print "---", ignorable for waif in ignorable: # - doomed: waif = os.path.join(path, waif) self._engine.LOG.debug("Deleting waif '%s'" % (waif, )) if not dry_run: try: os.remove(waif) except EnvironmentError, exc: self._engine.LOG.warn( "Problem deleting waif '%s' (%s)" % (waif, exc)) ##self._engine.LOG.debug("Deleting empty directory '%s'" % (path,)) doomed.update(remove_with_links(path)) # Delete item from engine if not dry_run: self.delete() def flush(self): """ Write volatile data to disk. """
def mainloop(self): """ The main loop. """ self._validate_config() config.engine.load_config() # Defaults for process control paths if not self.options.no_fork and not self.options.guard_file: self.options.guard_file = os.path.join(config.config_dir, "run/pyrotorque") if not self.options.pid_file: self.options.pid_file = os.path.join(config.config_dir, "run/pyrotorque.pid") # Process control if self.options.status or self.options.stop or self.options.restart: if self.options.pid_file and os.path.exists(self.options.pid_file): running, pid = osmagic.check_process(self.options.pid_file) else: running, pid = False, 0 if self.options.stop or self.options.restart: if running: os.kill(pid, signal.SIGTERM) # Wait for termination (max. 10 secs) for _ in range(100): running, _ = osmagic.check_process( self.options.pid_file) if not running: break time.sleep(.1) self.LOG.info("Process #%d stopped." % (pid)) elif pid: self.LOG.info("Process #%d NOT running anymore." % (pid)) else: self.LOG.info("No pid file '%s'" % (self.options.pid_file or "<N/A>")) else: self.LOG.info("Process #%d %s running." % (pid, "UP and" if running else "NOT")) if self.options.restart: if self.options.pid_file: running, pid = osmagic.check_process(self.options.pid_file) if running: self.return_code = error.EX_TEMPFAIL return else: self.return_code = error.EX_OK if running else error.EX_UNAVAILABLE return # Check for guard file and running daemon, abort if not OK try: osmagic.guard(self.options.pid_file, self.options.guard_file) except EnvironmentError as exc: self.LOG.debug(str(exc)) self.return_code = error.EX_TEMPFAIL return # Detach, if not disabled via option if not self.options.no_fork: # or getattr(sys.stdin, "isatty", lambda: False)(): osmagic.daemonize(pidfile=self.options.pid_file, logfile=logutil.get_logfile()) time.sleep(.05) # let things settle a little signal.signal(signal.SIGTERM, _raise_interrupt) # Set up services from apscheduler.scheduler import Scheduler self.sched = Scheduler(config.torque) self._init_wsgi_server() # Run services self.sched.start() try: self._add_jobs() # TODO: daemonize here, or before the scheduler starts? self._run_forever() finally: self.sched.shutdown() if self.wsgi_server: self.wsgi_server.task_dispatcher.shutdown() self.wsgi_server = None if self.options.pid_file: try: os.remove(self.options.pid_file) except EnvironmentError as exc: self.LOG.warn("Failed to remove pid file '%s' (%s)" % (self.options.pid_file, exc)) self.return_code = error.EX_IOERR