Exemple #1
0
    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, ))
Exemple #2
0
            )
            ##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"])
Exemple #3
0
                    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
Exemple #4
0
    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))
Exemple #5
0
    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.")
Exemple #6
0
    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()
Exemple #7
0
                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.
        """
Exemple #8
0
    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