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

Feature/add quality profiles #7293

Open
wants to merge 4 commits into
base: develop
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
78 changes: 78 additions & 0 deletions medusa/databases/main_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
import warnings

from medusa import common, db, subtitles
from medusa import app
from medusa.databases import utils
from medusa.helper.common import dateTimeFormat
from medusa.indexers.indexer_config import STATUS_MAP
from medusa.logger.adapters.style import BraceAdapter
from medusa.name_parser.parser import NameParser
from medusa.quality_profile import QualityProfile

from six import iteritems

Expand Down Expand Up @@ -898,4 +900,80 @@ def execute(self):
if not self.hasColumn('tv_shows', 'rls_ignore_exclude'):
self.addColumn('tv_shows', 'rls_ignore_exclude', 'NUMERIC', 0)

self.inc_minor_version()

class AddQualityProfiles(AddReleaseIgnoreRequireExludeOptions):
"""Add quality profiles tables."""

def test(self):
"""Test if the version is at least 44.15"""
return self.connection.version >= (44, 15)

def execute(self):
utils.backup_database(self.connection.path, self.connection.version)

if not self.hasTable('quality_profiles'):
log.info(u'Adding quality_profiles table')
self.connection.action('''
CREATE TABLE "quality_profiles" (
`quality_profile_id` INTEGER PRIMARY KEY AUTOINCREMENT,
`description` TEXT,
`enabled` INTEGER NOT NULL DEFAULT 0,
`defaultprofile` INTEGER NOT NULL DEFAULT 0);
''')

if not self.hasTable('quality_profile_options'):
log.info(u'Adding quality_profiles table')
self.connection.action('''
CREATE TABLE "quality_profile_options" (
`option_id` INTEGER PRIMARY KEY AUTOINCREMENT,
`quality_profile_id` INTEGER NOT NULL,
`allowed` INTEGER,
`preferred` INTEGER,
`size_min` INTEGER,
`size_max` INTEGER,
`rls_require_words` TEXT,
`rls_ignore_words` TEXT,
`rls_require_exclude` INTEGER,
`rls_ignore_exclude` INTEGER,
`priority` INTEGER );
''')

if not len(self.connection.select(
'SELECT * FROM quality_profiles'
)):
log.info(u'Adding the default (0) quality profile')
new_profile = QualityProfile(description='default', enabled=True, default=True, quality=app.QUALITY_DEFAULT)
new_profile.save()

# Add new field 'quality_profile_id'
log.info(u'Adding new quality_profile_id field in the tv_shows table')
# Set it's default value to 1, for now. Each tvshow can have a different composite quality, so we need to
# add these profiles on the fly.
if not self.hasColumn('tv_shows', 'quality_profile_id'):
self.addColumn('tv_shows', 'quality_profile_id', 'NUMERIC', 1)

# loop through each show and get it's current configured quality
show_qualities = {app.QUALITY_DEFAULT: 1}
qualities = self.connection.select('SELECT quality, show_id, show_name FROM tv_shows')
counter = 1
for row in qualities:
if row['quality'] not in show_qualities:
profile_description = 'migrated quality profile ({0})'.format(counter)
counter += 1

# Create the profile
log.info(u'Adding a new quality profile {profile_description} for show {show}',
{'profile_description': profile_description, 'show': row['show_name']})
profile = QualityProfile(description=profile_description, enabled=True, default=False,
quality=row['quality'])
profile.save()
show_qualities[row['quality']] = profile.profile_id

# Update the show's quality_profile_id field with the newly create profile_id
self.connection.action('UPDATE tv_shows SET quality_profile_id = ? WHERE show_id = ?',
[show_qualities[row['quality']], row['show_id']])



self.inc_minor_version()
5 changes: 4 additions & 1 deletion medusa/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ def upsert(self, tableName, valueDict, keyDict):
"""

changesBefore = self.connection.total_changes
action_result = None

def gen_params(my_dict):
return [x + ' = ?' for x in my_dict]
Expand All @@ -357,7 +358,9 @@ def gen_params(my_dict):
if self.connection.total_changes == changesBefore:
query = 'INSERT INTO [' + tableName + '] (' + ', '.join(list(valueDict) + list(keyDict)) + ')' + \
' VALUES (' + ', '.join(['?'] * len(list(valueDict) + list(keyDict))) + ')'
self.action(query, list(itervalues(valueDict)) + list(itervalues(keyDict)))
action_result = self.action(query, list(itervalues(valueDict)) + list(itervalues(keyDict)))

return action_result

def tableInfo(self, tableName):
"""
Expand Down
182 changes: 182 additions & 0 deletions medusa/quality_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
"""Quality profile module."""

import logging
from medusa import app, db
from medusa.common import Quality
from medusa.logger.adapters.style import BraceAdapter


logger = BraceAdapter(logging.getLogger(__name__))
logger.logger.addHandler(logging.NullHandler())


class QualityOption(object):
"""Quality option object."""
def __init__(
self,
option_id=None,
allowed=None,
preferred=None,
size_min=None,
size_max=None,
required_words=None,
ignored_words=None,
priority=None,
db_row=None,
profile=None
):
if db_row:
self.option_id = db_row['option_id']
self.profile_id = db_row['quality_profile_id']
self.allowed = db_row['allowed']
self.preferred = db_row['preferred']
self.size_min = db_row['size_min']
self.size_max = db_row['size_max']
self.priority = db_row['priority']

self.required_words = db_row['rls_require_words']
self.ignored_words = db_row['rls_ignore_words']
else:
self.option_id = option_id
self.allowed = allowed
self.preferred = preferred
self.size_min = size_min
self.size_max = size_max
self.priority = priority
self.required_words = required_words
self.ignored_words = ignored_words
self.profile = profile
if self.profile:
self.profile_id = self.profile.profile_id

def save(self):
"""Save the QualityOption to db."""
if self.profile:

key_dict = {'option_id': self.option_id}

value_dict = {
'quality_profile_id': self.profile.profile_id,
'allowed': self.allowed,
'preferred': self.preferred,
'size_min': self.size_min,
'size_max': self.size_max,
'priority': self.priority,
'rls_require_words': self.required_words,
'rls_ignore_words': self.ignored_words
}
self.profile.main_db.upsert('quality_profile_options', value_dict, key_dict)


class QualityProfile(object):
"""
Quality profile class.

Each profile can have multiple qualities, combined with ignored or required words, min and max size.
When the property `qualities` is used, and the array has a length, the other properties like, quality, size_min, size_max
are ignored.
"""
def __init__(self, profile_id=None, description=None, enabled=False, default=False, qualities=None, quality=None):
self.main_db = db.DBConnection()

self.profile_id = profile_id
self.description = description
self.enabled = enabled
self.default = default
self.qualities = qualities or []
self.quality = quality

if not profile_id is None:
self.load(profile_id)
elif not qualities and quality:
# Let's use the composed quality and generate quality options for migration purposes
self._create_quality_options()

def load(self, profile_id):
"""Load profile by id."""
quality_profile = self.main_db.select(
'SELECT * '
'FROM quality_profiles'
)

if not quality_profile:
raise Exception('Could not locate profile id with id: {0}'.format(profile_id))

self.description = quality_profile[0]['description']
self.enabled = quality_profile[0]['enabled']
self.default = quality_profile[0]['defaultprofile']

if len(quality_profile) > 0:
quality_options = self.main_db.select(
'SELECT * '
'FROM quality_profile_options '
'WHERE quality_profile_id = ?'
, [profile_id]
)

for option in quality_options:
self.qualities.append(QualityOption(db_row=option))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is each QualityOption added to the array qualities


def save(self):
"""Save current profile object."""
key_dict = {'quality_profile_id': self.profile_id}

value_dict = {
'description': self.description,
'enabled': self.enabled,
'defaultprofile': self.default
}
result = self.main_db.upsert('quality_profiles', value_dict, key_dict)
if result:
self.profile_id = result.lastrowid

# If there are qualities save them
for quality in self.qualities:
quality.save()

def _create(self, db_row):
"""Create Quality Profile object."""
pass

def _create_quality_options(self):
"""Create QualityOptions from the composed quality."""
allowed, preferred = Quality.split_quality(self.quality)
priority = 1
for quality in allowed:
self.qualities.append(
QualityOption(
allowed=quality,
priority=priority,
profile=self
)
)
priority += 1

priority = 1
for quality in preferred:
self.qualities.append(
QualityOption(
preferred=quality,
priority=priority,
profile=self
)
)
priority += 1

@property
def quality_allowed(self):
"""Combine all the allowed qualities from the self.qualities array."""
return Quality.combine_qualities([quality.allowed for quality in self.qualities if quality.allowed], [])

@property
def quality_preferred(self):
"""Combine all the preferred qualities from the self.qualities array."""
return Quality.combine_qualities([], [quality.preferred for quality in self.qualities if quality.preferred])

@property
def quality_composite(self):
"""Combine allowed and preferred qualities."""
return Quality.combine_qualities(
[quality.allowed for quality in self.qualities if quality.allowed],
[quality.preferred for quality in self.qualities if quality.preferred]
)
5 changes: 5 additions & 0 deletions medusa/tv/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
from medusa.tv.base import Identifier, TV
from medusa.tv.episode import Episode
from medusa.tv.indexer import Indexer
from medusa.quality_profile import QualityProfile

from six import iteritems, itervalues, string_types, text_type, viewitems

Expand Down Expand Up @@ -240,6 +241,7 @@ def __init__(self, indexer, indexerid, lang='', quality=None,
self.externals = {}
self._cached_indexer_api = None
self.plot = None
self.quality_profile = None

other_show = Show.find_by_id(app.showList, self.indexer, self.series_id)
if other_show is not None:
Expand Down Expand Up @@ -1523,6 +1525,9 @@ def _load_from_db(self):
# Load external id's from indexer_mappings table.
self.externals = load_externals_from_db(self.indexer, self.series_id)

# Load the quality profile from db
self.quality_profile = QualityProfile(profile_id=sql_results[0]['quality_profile_id'])

# Get IMDb_info from database
main_db_con = db.DBConnection()
sql_results = main_db_con.select(
Expand Down