Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added tag filter functionality to qBittorrent #192

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions autoremovetorrents/client/qbittorrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ def torrent_properties(self, torrent_hash):
torrent_obj.category = [torrent['category']] if len(torrent['category']) > 0 else []
elif 'label' in torrent:
torrent_obj.category = [torrent['label']] if len(torrent['label']) > 0 else []
# In qBittorrent class, inside the torrent_properties method:
torrent_obj.tags = torrent.get('tags', "").split(',') if 'tags' in torrent else []
torrent_obj.tracker = [tracker['url'] for tracker in trackers]
torrent_obj.status = qBittorrent._judge_status(torrent['state'])
torrent_obj.stalled = torrent['state'] == 'stalledUP' or torrent['state'] == 'stalledDL'
Expand Down
9 changes: 6 additions & 3 deletions autoremovetorrents/filter/filter.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#-*- coding:utf-8 -*-
# -*- coding:utf-8 -*-

class Filter(object):
def __init__(self, all_seeds, ac, re):
self._all = all_seeds
self._accept = ac
self._reject = re
self._accept = set(ac)
self._reject = set(re)

def __str__(self):
return f"Filter(all_seeds={self._all}, accept={self._accept}, reject={self._reject})"
33 changes: 33 additions & 0 deletions autoremovetorrents/filter/tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding:utf-8 -*-

from .filter import Filter

class TagFilter(Filter):
def __init__(self, all_tags, ac, re):
super().__init__(all_tags, ac, re)

def apply(self, torrents):
# Debugging: Print the tags for each torrent
# for torrent in torrents:
# print(f"Torrent: {torrent.name}, Tags: {torrent.tags}")

# Pick accepted torrents
accepts = set()
if self._all:
accepts = set(torrents)
elif len(self._accept) > 0:
for torrent in torrents:
for tag in torrent.tags:
if tag in self._accept:
accepts.add(torrent)

# Pick rejected torrents
rejects = set()
if len(self._reject) > 0:
for torrent in accepts:
for tag in torrent.tags:
if tag in self._reject:
rejects.add(torrent)

result = accepts.difference(rejects)
return result
27 changes: 16 additions & 11 deletions autoremovetorrents/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from .filter.category import CategoryFilter
from .filter.status import StatusFilter
from .filter.tracker import TrackerFilter
from .filter.tag import TagFilter

class Strategy(object):
def __init__(self, name, conf):
Expand All @@ -51,21 +52,24 @@ def __init__(self, name, conf):
else not 'trackers' in conf
self._all_status = conf['all_status'] if 'all_status' in conf \
else not 'status' in conf
self._all_tags = conf['all_tags'] if 'all_tags' in conf \
else not 'tags' in conf

# Print debug log
self._logger.debug("Configuration of strategy '%s':" % self._name)
self._logger.debug('Configurated filters and conditions: %s' % ', '.join(self._conf))
self._logger.debug('Configured filters and conditions: %s' % ', '.join(self._conf))

# Apply Filters
def _apply_filters(self):
filter_conf = [
{'all':self._all_categories, 'ac':'categories', 're':'excluded_categories'}, # Category filter
{'all':self._all_status, 'ac':'status', 're':'excluded_status'}, # Status filter
{'all':self._all_trackers, 'ac':'trackers', 're':'excluded_trackers'}, # Tracker filter
{'all': self._all_categories, 'ac': 'categories', 're': 'excluded_categories'}, # Category filter
{'all': self._all_status, 'ac': 'status', 're': 'excluded_status'}, # Status filter
{'all': self._all_trackers, 'ac': 'trackers', 're': 'excluded_trackers'}, # Tracker filter
{'all': self._all_tags, 'ac': 'tags', 're': 'excluded_tags'}, # Tags filter
]
filter_obj = [CategoryFilter, StatusFilter, TrackerFilter]
filter_obj = [CategoryFilter, StatusFilter, TrackerFilter, TagFilter]

for i in range(0, len(filter_conf)):
for i in range(len(filter_conf)):
# Initialize all of the filter arguments
# User can use a single line to represent one item instead of using list
accept_field = filter_conf[i]['ac']
Expand All @@ -82,7 +86,7 @@ def _apply_filters(self):

# Print debug log
self._logger.debug('Applying filter %s...' % filter_obj[i].__name__)
self._logger.debug('Filter configrations: ALL: %s; ACCEPTANCES: [%s]; REJECTIONS: [%s].' % (
self._logger.debug('Filter configurations: ALL: %s; ACCEPTANCES: [%s]; REJECTIONS: [%s].' % (
filter_conf[i]['all'],
', '.join(self._conf[accept_field]),
', '.join(self._conf[reject_field])
Expand Down Expand Up @@ -132,6 +136,7 @@ def _apply_conditions(self, client_status):
'max_size': SizeCondition,
'upload_ratio': UploadRatioCondition,
}

for conf in self._conf:
if conf in conditions:
# Print debug log
Expand All @@ -149,7 +154,6 @@ def _apply_conditions(self, client_status):
"%s. Your client may not support this property, so the condition %s does not work." % \
(str(e), conf)
)

# Output
self.remain_list = cond.remain
self.remove_list.update(cond.remove)
Expand All @@ -161,7 +165,6 @@ def _apply_conditions(self, client_status):
self._logger.debug('OUTPUT: %d torrent(s) to be removed after applying the condition.' % len(self.remove_list))
for torrent in self.remove_list:
self._logger.debug(torrent)

# Execute this strategy
def execute(self, client_status, torrents):
self._logger.info('Running strategy %s...' % self._name)
Expand All @@ -172,8 +175,10 @@ def execute(self, client_status, torrents):
self._apply_conditions(client_status)
# Print remove list
self._logger.info("Total: %d torrent(s). %d torrent(s) can be removed." %
(len(self.remain_list)+len(self.remove_list), len(self.remove_list)))
(len(self.remain_list) + len(self.remove_list), len(self.remove_list)))
if len(self.remove_list) > 0:
self._logger.info('To be deleted:')
for torrent in self.remove_list:
self._logger.info(torrent)
self._logger.info(torrent)
else:
self._logger.info('No torrents to be deleted.')
31 changes: 26 additions & 5 deletions autoremovetorrents/torrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,30 @@
class Torrent(object):
def __init__(self):
# Proper attributes:
# hash, name, category, tracker, status, size, ratio, uploaded, create_time, seeding_time
# NOTE: The attribute 'last_activity' stores the time interval since last activity,
# not the unix timestamp of last activity.
pass
# hash, name, category, tracker, status, size, ratio, uploaded, create_time, seeding_time,
# last_activity, progress, tags, download_speed, upload_speed, seeder, leecher,
# connected_seeder, connected_leecher, average_upload_speed, average_download_speed
self.hash = ""
self.name = ""
self.category = []
self.tags = []
self.tracker = []
self.status = None
self.size = 0
self.ratio = 0.0
self.uploaded = 0
self.create_time = 0
self.seeding_time = 0
self.download_speed = 0
self.upload_speed = 0
self.seeder = 0
self.connected_seeder = 0
self.leecher = 0
self.connected_leecher = 0
self.average_upload_speed = 0
self.average_download_speed = 0
self.last_activity = 0
self.progress = 0.0

# Format torrent info
def __str__(self):
Expand All @@ -30,7 +50,7 @@ def disp(prop, converter = None):
"\tSeeder(connected/total):%d/%d\tLeecher(connected/total):%d/%d\tStatus:%s\n" +
"\tDownload Speed:%s(Avg.:%s)\tUpload Speed:%s(Avg.:%s)\n" +
"\tCreate Time:%s\tSeeding Time:%s\tDownloading Time:%s\tLast Activity:%s\n" +
"\tCategory:%s\tTracker:%s") % \
"\tCategory:%s\tTags:%s\tTracker:%s") % \
(
disp('name'),
disp('progress', lambda x: x*100),
Expand All @@ -51,6 +71,7 @@ def disp(prop, converter = None):
disp('downloading_time', convert_seconds),
disp('last_activity', convert_seconds),
disp('category', ','.join),
disp('tags', ','.join),
disp('tracker', lambda t: \
','.join(
[urlparse_(x).hostname if urlparse_(x).hostname is not None else x for x in t]
Expand Down