# -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 import json import sqlalchemy as sa from changelog import Changelog from db import MutableDict from queryparser import Parser import db import json_pickler import settings import state import logging logger = logging.getLogger(__name__) class User(db.Model): __tablename__ = 'user' created = sa.Column(sa.DateTime()) modified = sa.Column(sa.DateTime()) id = sa.Column(sa.String(43), primary_key=True) info = sa.Column(MutableDict.as_mutable(sa.PickleType(pickler=json_pickler))) nickname = sa.Column(sa.String(256), unique=True) pending = sa.Column(sa.String(64)) # sent|received queued = sa.Column(sa.Boolean()) peered = sa.Column(sa.Boolean()) online = sa.Column(sa.Boolean()) def __repr__(self): return self.id @classmethod def get(cls, id): return cls.query.filter_by(id=id).first() @classmethod def get_or_create(cls, id): user = cls.get(id) if not user: user = cls(id=id, peered=False, online=False) user.info = {} user.update_name() user.save() return user def save(self): state.db.session.add(self) state.db.session.commit() @property def name(self): name = self.nickname if self.id != settings.USER_ID else '' return name def json(self): j = {} if self.info: j.update(self.info) j['id'] = self.id if self.pending: j['pending'] = self.pending j['peered'] = self.peered j['online'] = self.is_online() j['nickname'] = self.info.get('nickname') j['username'] = self.info.get('username') if self.id != settings.USER_ID else settings.preferences['username'] j['name'] = self.name return j def is_online(self): return state.nodes and state.nodes.is_online(self.id) def lists_json(self): return [{ 'id': '%s:' % ('' if self.id == settings.USER_ID else self.nickname), 'name': 'Library', 'type': 'library', 'items': self.items.count(), 'user': self.name }] + [l.json() for l in self.lists.order_by('index_')] def update_peering(self, peered, username=None): was_peering = self.peered if peered: self.pending = '' if username: self.info['username'] = username self.update_name() # FIXME: need to set peered to False to not trigger changelog event # before other side receives acceptPeering request self.peered = False self.save() if not was_peering: Changelog.record(state.user(), 'addpeer', self.id, self.nickname) self.peered = True self.queued = True self.save() else: self.pending = '' self.peered = False self.update_name() self.save() List.query.filter_by(user_id=self.id).delete() for i in self.items: i.users.remove(self) if not i.users: i.delete() Changelog.query.filter_by(user_id=self.id).delete() self.save() if was_peering: Changelog.record(state.user(), 'removepeer', self.id) self.save() def update_name(self): if self.id == settings.USER_ID: name = settings.preferences.get('username', 'anonymous') else: name = self.info.get('nickname') or self.info.get('username') or 'anonymous' nickname = name n = 2 while self.query.filter_by(nickname=nickname).filter(User.id!=self.id).first(): nickname = '%s [%d]' % (name, n) n += 1 self.nickname = nickname def migrate_id(self, service_id): if len(service_id) == 16: statements = [ "DELETE FROM user WHERE id = '{nid}'", "UPDATE user SET id = '{nid}' WHERE id = '{oid}'", "UPDATE list SET user_id = '{nid}' WHERE user_id = '{oid}'", "UPDATE useritem SET user_id = '{nid}' WHERE user_id = '{oid}'", "UPDATE changelog SET user_id = '{nid}' WHERE user_id = '{oid}'", ] with db.session() as session: for sql in statements: session.connection().execute(sql.format(oid=self.id, nid=service_id)) session.commit() list_items = sa.Table('listitem', db.metadata, sa.Column('list_id', sa.Integer(), sa.ForeignKey('list.id')), sa.Column('item_id', sa.String(32), sa.ForeignKey('item.id')) ) class List(db.Model): __tablename__ = 'list' id = sa.Column(sa.Integer(), primary_key=True) name = sa.Column(sa.String()) index_ = sa.Column(sa.Integer()) type = sa.Column(sa.String(64)) _query = sa.Column('query', MutableDict.as_mutable(sa.PickleType(pickler=json_pickler))) user_id = sa.Column(sa.String(43), sa.ForeignKey('user.id')) user = sa.orm.relationship('User', backref=sa.orm.backref('lists', lazy='dynamic')) items = sa.orm.relationship('Item', secondary=list_items, backref=sa.orm.backref('lists', lazy='dynamic')) @classmethod def get(cls, user_id, name=None): if name is None: user_id, name = cls.get_user_name(user_id) return cls.query.filter_by(user_id=user_id, name=name).first() @classmethod def get_user_name(cls, user_id): nickname, name = user_id.split(':', 1) if nickname: user = User.query.filter_by(nickname=nickname).first() user_id = user.id else: user_id = settings.USER_ID return user_id, name @classmethod def get_or_create(cls, user_id, name=None, query=None): if name is None: user_id, name = cls.get_user_name(user_id) l = cls.get(user_id, name) if not l: l = cls.create(user_id, name, query) return l @classmethod def create(cls, user_id, name, query=None): prefix = name n = 2 while cls.get(user_id, name): name = '%s [%s]' % (prefix, n) n += 1 l = cls(user_id=user_id, name=name) l._query = query l.type = 'smart' if l._query else 'static' l.index_ = cls.query.filter_by(user_id=user_id).count() state.db.session.add(l) state.db.session.commit() if user_id == settings.USER_ID: if not l._query: Changelog.record(state.user(), 'addlist', l.name) return l @classmethod def rename_user(cls, old, new): for l in cls.query.filter(cls._query!=None): def update_conditions(conditions): changed = False for c in conditions: if 'conditions' in c: changed = update_conditions(conditions) else: if c.get('key') == 'list' and c.get('value', '').startswith('%s:' % old): c['value'] = '%s:%s' % new, c['value'].split(':', 1)[1] changed = True return changed if update_conditions(l._query.get('conditions', [])): l.save() def add_items(self, items): from item.models import Item for item_id in items: i = Item.get(item_id) if i: self.items.append(i) if self.user_id == settings.USER_ID: i.queue_download() i.update() state.db.session.add(self) state.db.session.commit() if self.user_id == settings.USER_ID: Changelog.record(self.user, 'addlistitems', self.name, items) def remove_items(self, items): from item.models import Item for item_id in items: i = Item.get(item_id) if i in self.items: self.items.remove(i) i.update() state.db.session.add(self) state.db.session.commit() if self.user_id == settings.USER_ID: Changelog.record(self.user, 'removelistitems', self.name, items) def remove(self): if not self._query: for i in self.items: self.items.remove(i) if not self._query: if self.user_id == settings.USER_ID: Changelog.record(self.user, 'removelist', self.name) state.db.session.delete(self) state.db.session.commit() @property def public_id(self): id = '' if self.user_id != settings.USER_ID: id += self.user.nickname id = '%s:%s' % (id, self.name) return id @property def find_id(self): id = '' if self.user_id != settings.USER_ID: id += self.user_id id = '%s:%s' % (id, self.id) return id def __repr__(self): return self.public_id def items_count(self): key = 'list:' + self.public_id value = state.cache.get(key) if value is None: from item.models import Item if self._query: data = self._query value = Parser(Item).find({'query': data}).count() else: value = len(self.items) state.cache.set(key, value) return value def json(self): r = { 'id': self.public_id, 'user': self.user.name, 'name': self.name, 'index': self.index_, 'items': self.items_count(), 'type': self.type } if self.type == 'smart': r['query'] = self._query return r def save(self): state.db.session.add(self) state.db.session.commit()