You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

419 lines
12 KiB

from UserDict import DictMixin
from collections import OrderedDict
from couchpotato.core.helpers.encoding import toUnicode
from elixir.entity import Entity
from elixir.fields import Field
from elixir.options import options_defaults, using_options
from elixir.relationships import ManyToMany, OneToMany, ManyToOne
from sqlalchemy.ext.mutable import Mutable
from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, String, \
TypeDecorator
import json
import time
options_defaults["shortnames"] = True
# We would like to be able to create this schema in a specific database at
# will, so we can test it easily.
# Make elixir not bind to any session to make this possible.
#
# http://elixir.ematia.de/trac/wiki/Recipes/MultipleDatabasesOneMetadata
__session__ = None
class SetEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, set):
return list(obj)
return json.JSONEncoder.default(self, obj)
class JsonType(TypeDecorator):
impl = UnicodeText
def process_bind_param(self, value, dialect):
try:
return toUnicode(json.dumps(value, cls = SetEncoder))
except:
try:
return toUnicode(json.dumps(value, cls = SetEncoder, encoding = 'latin-1'))
except:
raise
def process_result_value(self, value, dialect):
return json.loads(value if value else '{}')
class MutableDict(Mutable, dict):
@classmethod
def coerce(cls, key, value):
if not isinstance(value, MutableDict):
if isinstance(value, dict):
return MutableDict(value)
return Mutable.coerce(key, value)
else:
return value
def __delitem(self, key):
dict.__delitem__(self, key)
self.changed()
def __setitem__(self, key, value):
dict.__setitem__(self, key, value)
self.changed()
def __getstate__(self):
return dict(self)
def __setstate__(self, state):
self.update(self)
def update(self, *args, **kwargs):
super(MutableDict, self).update(*args, **kwargs)
self.changed()
MutableDict.associate_with(JsonType)
class Media(Entity):
"""Media Resource could have multiple releases
The files belonging to the media object are global for the whole media
such as trailers, nfo, thumbnails"""
type = Field(String(10), default = "movie", index = True)
last_edit = Field(Integer, default = lambda: int(time.time()), index = True)
library = ManyToOne('Library', cascade = 'delete, delete-orphan', single_parent = True)
status = ManyToOne('Status')
profile = ManyToOne('Profile')
category = ManyToOne('Category')
releases = OneToMany('Release', cascade = 'all, delete-orphan')
files = ManyToMany('File', cascade = 'all, delete-orphan', single_parent = True)
class Library(Entity):
""""""
using_options(inheritance = 'multi')
# For Movies, CPS uses three: omdbapi (no prio !?), tmdb (prio 2) and couchpotatoapi (prio 1)
type = Field(String(10), default = "movie", index = True)
primary_provider = Field(String(10), default = "imdb", index = True)
year = Field(Integer)
identifier = Field(String(40), index = True)
plot = Field(UnicodeText)
tagline = Field(UnicodeText(255))
info = Field(JsonType)
status = ManyToOne('Status')
media = OneToMany('Media', cascade = 'all, delete-orphan')
titles = OneToMany('LibraryTitle', cascade = 'all, delete-orphan')
files = ManyToMany('File', cascade = 'all, delete-orphan', single_parent = True)
parent = ManyToOne('Library')
children = OneToMany('Library')
class ShowLibrary(Library, DictMixin):
using_options(inheritance = 'multi')
last_updated = Field(Integer, index = True)
show_status = Field(String(10), index = True)
# XXX: Maybe we should convert this to seconds?
# airs_time u'21:00'
airs_time = Field(Unicode, index = True)
# airs_dayofweek = Field(Integer, index = True)
# u'Monday': 1,
# u'Tuesday': 2,
# u'Wednesday': 4,
# u'Thursday': 8,
# u'Friday': 16,
# u'Saturday': 32,
# u'Sunday': 64,
# u'Daily': 127,
airs_dayofweek = Field(Integer, index = True)
def getSeasons(self):
data = OrderedDict()
for c in self.children:
data[c.season_number] = c
return data
def getEpisodes(self, season_number):
data = OrderedDict()
for c in self.children[season_number].children:
data[c.episode_number] = c
return data
# Read access to season by number: library[1] for season 1
data = {}
def __getitem__(self, key):
if not self.data:
self.setData()
if key in self.data:
return self.data[key]
if hasattr(self.__class__, "__missing__"):
return self.__class__.__missing__(self, key)
raise KeyError(key)
def get(self, key, failobj = None):
if key not in self:
return failobj
return self[key]
def keys(self): return self.data.keys()
def setData(self):
for c in self.children:
self.data[c.season_number] = c
class SeasonLibrary(Library, DictMixin):
using_options(inheritance = 'multi')
season_number = Field(Integer, index = True)
last_updated = Field(Integer, index = True)
def getEpisodes(self):
data = OrderedDict()
for c in self.children:
data[c.episode_number] = c
return data
# Read access episode by number: library[1][4] for season 1, episode 4
data = {}
def __getitem__(self, key):
if not self.data:
self.setData()
if key in self.data:
return self.data[key]
if hasattr(self.__class__, "__missing__"):
return self.__class__.__missing__(self, key)
raise KeyError(key)
def get(self, key, failobj = None):
if key not in self:
return failobj
return self[key]
def keys(self): return self.data.keys()
def setData(self):
for c in self.children:
self.data[c.episode_number] = c
class EpisodeLibrary(Library):
using_options(inheritance = 'multi')
last_updated = Field(Integer, index = True)
season_number = Field(Integer, index = True)
episode_number = Field(Integer, index = True)
absolute_number = Field(Integer, index = True)
class LibraryTitle(Entity):
""""""
using_options(order_by = '-default')
title = Field(Unicode)
simple_title = Field(Unicode, index = True)
default = Field(Boolean, default = False, index = True)
language = OneToMany('Language')
libraries = ManyToOne('Library')
class Language(Entity):
""""""
identifier = Field(String(20), index = True)
label = Field(Unicode)
titles = ManyToOne('LibraryTitle')
class Release(Entity):
"""Logically groups all files that belong to a certain release, such as
parts of a movie, subtitles."""
last_edit = Field(Integer, default = lambda: int(time.time()), index = True)
identifier = Field(String(100), index = True)
media = ManyToOne('Media')
status = ManyToOne('Status')
quality = ManyToOne('Quality')
files = ManyToMany('File')
info = OneToMany('ReleaseInfo', cascade = 'all, delete-orphan')
def to_dict(self, deep = None, exclude = None):
if not exclude: exclude = []
if not deep: deep = {}
orig_dict = super(Release, self).to_dict(deep = deep, exclude = exclude)
new_info = {}
for info in orig_dict.get('info', []):
value = info['value']
try: value = int(info['value'])
except: pass
new_info[info['identifier']] = value
orig_dict['info'] = new_info
return orig_dict
class ReleaseInfo(Entity):
"""Properties that can be bound to a file for off-line usage"""
identifier = Field(String(50), index = True)
value = Field(Unicode(255), nullable = False)
release = ManyToOne('Release')
class Status(Entity):
"""The status of a release, such as Downloaded, Deleted, Wanted etc"""
identifier = Field(String(20), unique = True)
label = Field(Unicode(20))
releases = OneToMany('Release')
class Quality(Entity):
"""Quality name of a release, DVD, 720p, DVD-Rip etc"""
using_options(order_by = 'order')
identifier = Field(String(20), unique = True)
label = Field(Unicode(20))
order = Field(Integer, default = 0, index = True)
size_min = Field(Integer)
size_max = Field(Integer)
releases = OneToMany('Release')
profile_types = OneToMany('ProfileType')
class Profile(Entity):
""""""
using_options(order_by = 'order')
label = Field(Unicode(50))
order = Field(Integer, default = 0, index = True)
core = Field(Boolean, default = False)
hide = Field(Boolean, default = False)
media = OneToMany('Media')
types = OneToMany('ProfileType', cascade = 'all, delete-orphan')
def to_dict(self, deep = None, exclude = None):
if not exclude: exclude = []
if not deep: deep = {}
orig_dict = super(Profile, self).to_dict(deep = deep, exclude = exclude)
orig_dict['core'] = orig_dict.get('core') or False
orig_dict['hide'] = orig_dict.get('hide') or False
return orig_dict
class Category(Entity):
""""""
using_options(order_by = 'order')
label = Field(Unicode(50))
order = Field(Integer, default = 0, index = True)
required = Field(Unicode(255))
preferred = Field(Unicode(255))
ignored = Field(Unicode(255))
destination = Field(Unicode(255))
media = OneToMany('Media')
destination = Field(Unicode(255))
class ProfileType(Entity):
""""""
using_options(order_by = 'order')
order = Field(Integer, default = 0, index = True)
finish = Field(Boolean, default = True)
wait_for = Field(Integer, default = 0)
quality = ManyToOne('Quality')
profile = ManyToOne('Profile')
class File(Entity):
"""File that belongs to a release."""
path = Field(Unicode(255), nullable = False, unique = True)
part = Field(Integer, default = 1)
available = Field(Boolean, default = True)
type = ManyToOne('FileType')
properties = OneToMany('FileProperty')
history = OneToMany('RenameHistory')
media = ManyToMany('Media')
release = ManyToMany('Release')
library = ManyToMany('Library')
class FileType(Entity):
"""Types could be trailer, subtitle, movie, partial movie etc."""
identifier = Field(String(20), unique = True)
type = Field(Unicode(20))
name = Field(Unicode(50), nullable = False)
files = OneToMany('File')
class FileProperty(Entity):
"""Properties that can be bound to a file for off-line usage"""
identifier = Field(String(20), index = True)
value = Field(Unicode(255), nullable = False)
file = ManyToOne('File')
class RenameHistory(Entity):
"""Remembers from where to where files have been moved."""
old = Field(Unicode(255))
new = Field(Unicode(255))
file = ManyToOne('File')
class Notification(Entity):
using_options(order_by = 'added')
added = Field(Integer, default = lambda: int(time.time()))
read = Field(Boolean, default = False)
message = Field(Unicode(255))
data = Field(JsonType)
class Properties(Entity):
identifier = Field(String(50), index = True)
value = Field(Unicode(255), nullable = False)
def setup():
"""Setup the database and create the tables that don't exists yet"""
from elixir import setup_all, create_all
from couchpotato.environment import Env
engine = Env.getEngine()
setup_all()
create_all(engine)
try:
engine.execute("PRAGMA journal_mode = WAL")
engine.execute("PRAGMA temp_store = MEMORY")
except:
pass