378 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			378 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from webob.dec import wsgify
							 | 
						||
| 
								 | 
							
								from webob.exc import *
							 | 
						||
| 
								 | 
							
								from webob import Response
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import anki
							 | 
						||
| 
								 | 
							
								from anki.sync import HttpSyncServer, CHUNK_SIZE
							 | 
						||
| 
								 | 
							
								from anki.db import sqlite
							 | 
						||
| 
								 | 
							
								from anki.utils import checksum
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import AnkiServer.deck
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import MySQLdb
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								try:
							 | 
						||
| 
								 | 
							
								    import simplejson as json
							 | 
						||
| 
								 | 
							
								except ImportError:
							 | 
						||
| 
								 | 
							
								    import json
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import os, zlib, tempfile, time
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def makeArgs(mdict):
							 | 
						||
| 
								 | 
							
								    d = dict(mdict.items())
							 | 
						||
| 
								 | 
							
								    # TODO: use password/username/version for something?
							 | 
						||
| 
								 | 
							
								    for k in ['p','u','v','d']:
							 | 
						||
| 
								 | 
							
								        if d.has_key(k):
							 | 
						||
| 
								 | 
							
								            del d[k]
							 | 
						||
| 
								 | 
							
								    return d
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class FileIterable(object):
							 | 
						||
| 
								 | 
							
								    def __init__(self, fn):
							 | 
						||
| 
								 | 
							
								        self.fn = fn
							 | 
						||
| 
								 | 
							
								    def __iter__(self):
							 | 
						||
| 
								 | 
							
								        return FileIterator(self.fn)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class FileIterator(object):
							 | 
						||
| 
								 | 
							
								    def __init__(self, fn):
							 | 
						||
| 
								 | 
							
								        self.fn = fn
							 | 
						||
| 
								 | 
							
								        self.fo = open(self.fn, 'rb')
							 | 
						||
| 
								 | 
							
								        self.c = zlib.compressobj()
							 | 
						||
| 
								 | 
							
								        self.flushed = False
							 | 
						||
| 
								 | 
							
								    def __iter__(self):
							 | 
						||
| 
								 | 
							
								        return self
							 | 
						||
| 
								 | 
							
								    def next(self):
							 | 
						||
| 
								 | 
							
								        data = self.fo.read(CHUNK_SIZE)
							 | 
						||
| 
								 | 
							
								        if not data:
							 | 
						||
| 
								 | 
							
								            if not self.flushed:
							 | 
						||
| 
								 | 
							
								                self.flushed = True
							 | 
						||
| 
								 | 
							
								                return self.c.flush()
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                raise StopIteration
							 | 
						||
| 
								 | 
							
								        return self.c.compress(data)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def lock_deck(path):
							 | 
						||
| 
								 | 
							
								    """ Gets exclusive access to this deck path.  If there is a DeckThread running on this
							 | 
						||
| 
								 | 
							
								    deck, this will wait for its current operations to complete before temporarily stopping
							 | 
						||
| 
								 | 
							
								    it. """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    from AnkiServer.deck import thread_pool
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if thread_pool.decks.has_key(path):
							 | 
						||
| 
								 | 
							
								        thread_pool.decks[path].stop_and_wait()
							 | 
						||
| 
								 | 
							
								    thread_pool.lock(path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def unlock_deck(path):
							 | 
						||
| 
								 | 
							
								    """ Release exclusive access to this deck path. """
							 | 
						||
| 
								 | 
							
								    from AnkiServer.deck import thread_pool
							 | 
						||
| 
								 | 
							
								    thread_pool.unlock(path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SyncAppHandler(HttpSyncServer):
							 | 
						||
| 
								 | 
							
								    operations = ['summary','applyPayload','finish','createDeck','getOneWayPayload']
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self):
							 | 
						||
| 
								 | 
							
								        HttpSyncServer.__init__(self)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def createDeck(self, name):
							 | 
						||
| 
								 | 
							
								        # The HttpSyncServer.createDeck doesn't return a valid value!  This seems to be
							 | 
						||
| 
								 | 
							
								        # a bug in libanki.sync ...
							 | 
						||
| 
								 | 
							
								        return self.stuff({"status": "OK"})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def finish(self):
							 | 
						||
| 
								 | 
							
								        # The HttpSyncServer has no finish() function...  I can only assume this is a bug too!
							 | 
						||
| 
								 | 
							
								        return self.stuff("OK")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SyncApp(object):
							 | 
						||
| 
								 | 
							
								    valid_urls = SyncAppHandler.operations + ['getDecks','fullup','fulldown']
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, **kw):
							 | 
						||
| 
								 | 
							
								        self.data_root = os.path.abspath(kw.get('data_root', '.'))
							 | 
						||
| 
								 | 
							
								        self.base_url  = kw.get('base_url', '/')
							 | 
						||
| 
								 | 
							
								        self.users = {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # make sure the base_url has a trailing slash
							 | 
						||
| 
								 | 
							
								        if len(self.base_url) == 0:
							 | 
						||
| 
								 | 
							
								            self.base_url = '/'
							 | 
						||
| 
								 | 
							
								        elif self.base_url[-1] != '/':
							 | 
						||
| 
								 | 
							
								            self.base_url = base_url + '/'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # setup mysql connection
							 | 
						||
| 
								 | 
							
								        mysql_args = {}
							 | 
						||
| 
								 | 
							
								        for k, v in kw.items():
							 | 
						||
| 
								 | 
							
								            if k.startswith('mysql.'):
							 | 
						||
| 
								 | 
							
								                mysql_args[k[6:]] = v
							 | 
						||
| 
								 | 
							
								        self.mysql_args = mysql_args
							 | 
						||
| 
								 | 
							
								        self.conn = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # get SQL statements
							 | 
						||
| 
								 | 
							
								        self.sql_check_password = kw.get('sql_check_password')
							 | 
						||
| 
								 | 
							
								        self.sql_username2dirname = kw.get('sql_username2dirname')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    default_libanki_version = '.'.join(anki.version.split('.')[:2])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def user_libanki_version(self, u):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            s = self.users[u]['libanki']
							 | 
						||
| 
								 | 
							
								        except KeyError:
							 | 
						||
| 
								 | 
							
								            return self.default_libanki_version
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        parts = s.split('.')
							 | 
						||
| 
								 | 
							
								        if parts[0] == '1':
							 | 
						||
| 
								 | 
							
								            if parts[1] == '0':
							 | 
						||
| 
								 | 
							
								                return '1.0'
							 | 
						||
| 
								 | 
							
								            elif parts[1] in ('1','2'):
							 | 
						||
| 
								 | 
							
								                return '1.2'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return self.default_libanki_version
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Mimcs from anki.sync.SyncTools.stuff()
							 | 
						||
| 
								 | 
							
								    def _stuff(self, data):
							 | 
						||
| 
								 | 
							
								        return zlib.compress(json.dumps(data))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _connect_mysql(self):
							 | 
						||
| 
								 | 
							
								        if self.conn is None and len(self.mysql_args) > 0:
							 | 
						||
| 
								 | 
							
								            self.conn = MySQLdb.connect(**self.mysql_args)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _execute_sql(self, sql, args=()):
							 | 
						||
| 
								 | 
							
								        self._connect_mysql()
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            cur = self.conn.cursor()
							 | 
						||
| 
								 | 
							
								            cur.execute(sql, args)
							 | 
						||
| 
								 | 
							
								        except MySQLdb.OperationalError, e:
							 | 
						||
| 
								 | 
							
								            if e.args[0] == 2006:
							 | 
						||
| 
								 | 
							
								                # MySQL server has gone away message
							 | 
						||
| 
								 | 
							
								                self.conn = None
							 | 
						||
| 
								 | 
							
								                self._connect_mysql()
							 | 
						||
| 
								 | 
							
								                cur = self.conn.cursor()
							 | 
						||
| 
								 | 
							
								                cur.execute(sql, args)
							 | 
						||
| 
								 | 
							
								        return cur
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def check_password(self, username, password):
							 | 
						||
| 
								 | 
							
								        if len(self.mysql_args) > 0 and self.sql_check_password is not None:
							 | 
						||
| 
								 | 
							
								            cur = self._execute_sql(self.sql_check_password, (username, password))
							 | 
						||
| 
								 | 
							
								            row = cur.fetchone()
							 | 
						||
| 
								 | 
							
								            return row is not None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def username2dirname(self, username):
							 | 
						||
| 
								 | 
							
								        if len(self.mysql_args) > 0 and self.sql_username2dirname is not None:
							 | 
						||
| 
								 | 
							
								            cur = self._execute_sql(self.sql_username2dirname, (username,))
							 | 
						||
| 
								 | 
							
								            row = cur.fetchone()
							 | 
						||
| 
								 | 
							
								            if row is None:
							 | 
						||
| 
								 | 
							
								                return None
							 | 
						||
| 
								 | 
							
								            return str(row[0])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return username
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _getDecks(self, user_path):
							 | 
						||
| 
								 | 
							
								        decks = {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if os.path.exists(user_path):
							 | 
						||
| 
								 | 
							
								            # It is a dict of {'deckName':[modified,lastSync]}
							 | 
						||
| 
								 | 
							
								            for fn in os.listdir(unicode(user_path, 'utf-8')):
							 | 
						||
| 
								 | 
							
								                if len(fn) > 5 and fn[-5:] == '.anki':
							 | 
						||
| 
								 | 
							
								                    d = os.path.abspath(os.path.join(user_path, fn))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                    # For simplicity, we will always open a thread.  But this probably
							 | 
						||
| 
								 | 
							
								                    # isn't necessary!
							 | 
						||
| 
								 | 
							
								                    thread = AnkiServer.deck.thread_pool.start(d)
							 | 
						||
| 
								 | 
							
								                    def lookupModifiedLastSync(wrapper):
							 | 
						||
| 
								 | 
							
								                        deck = wrapper.open()
							 | 
						||
| 
								 | 
							
								                        return [deck.modified, deck.lastSync]
							 | 
						||
| 
								 | 
							
								                    res = thread.execute(lookupModifiedLastSync, [thread.wrapper])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#                    if thread_pool.threads.has_key(d):
							 | 
						||
| 
								 | 
							
								#                        thread = thread_pool.threads[d]
							 | 
						||
| 
								 | 
							
								#                        def lookupModifiedLastSync(wrapper):
							 | 
						||
| 
								 | 
							
								#                            deck = wrapper.open()
							 | 
						||
| 
								 | 
							
								#                            return [deck.modified, deck.lastSync]
							 | 
						||
| 
								 | 
							
								#                        res = thread.execute(lookup, [thread.wrapper])
							 | 
						||
| 
								 | 
							
								#                    else:
							 | 
						||
| 
								 | 
							
								#                        conn = sqlite.connect(d)
							 | 
						||
| 
								 | 
							
								#                        cur = conn.cursor()
							 | 
						||
| 
								 | 
							
								#                        cur.execute("select modified, lastSync from decks")
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#                        res = list(cur.fetchone())
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#                        cur.close()
							 | 
						||
| 
								 | 
							
								#                        conn.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                    #self.decks[fn[:-5]] = ["%.5f" % x for x in res]
							 | 
						||
| 
								 | 
							
								                    decks[fn[:-5]] = res
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # same as HttpSyncServer.getDecks()
							 | 
						||
| 
								 | 
							
								        return self._stuff({
							 | 
						||
| 
								 | 
							
								            "status": "OK",
							 | 
						||
| 
								 | 
							
								            "decks": decks,
							 | 
						||
| 
								 | 
							
								            "timestamp": time.time(),
							 | 
						||
| 
								 | 
							
								            })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _fullup(self, wrapper, infile, version):
							 | 
						||
| 
								 | 
							
								        wrapper.close()
							 | 
						||
| 
								 | 
							
								        path = wrapper.path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # DRS: most of this function was graciously copied
							 | 
						||
| 
								 | 
							
								        # from anki.sync.SyncTools.fullSyncFromServer()
							 | 
						||
| 
								 | 
							
								        (fd, tmpname) = tempfile.mkstemp(dir=os.getcwd(), prefix="fullsync")
							 | 
						||
| 
								 | 
							
								        outfile = open(tmpname, 'wb')
							 | 
						||
| 
								 | 
							
								        decomp = zlib.decompressobj()
							 | 
						||
| 
								 | 
							
								        while 1:
							 | 
						||
| 
								 | 
							
								            data = infile.read(CHUNK_SIZE)
							 | 
						||
| 
								 | 
							
								            if not data:
							 | 
						||
| 
								 | 
							
								                outfile.write(decomp.flush())
							 | 
						||
| 
								 | 
							
								                break
							 | 
						||
| 
								 | 
							
								            outfile.write(decomp.decompress(data))
							 | 
						||
| 
								 | 
							
								        infile.close()
							 | 
						||
| 
								 | 
							
								        outfile.close()
							 | 
						||
| 
								 | 
							
								        os.close(fd)
							 | 
						||
| 
								 | 
							
								        # if we were successful, overwrite old deck
							 | 
						||
| 
								 | 
							
								        if os.path.exists(path):
							 | 
						||
| 
								 | 
							
								            os.unlink(path)
							 | 
						||
| 
								 | 
							
								        os.rename(tmpname, path)
							 | 
						||
| 
								 | 
							
								        # reset the deck name
							 | 
						||
| 
								 | 
							
								        c = sqlite.connect(path)
							 | 
						||
| 
								 | 
							
								        lastSync = time.time()
							 | 
						||
| 
								 | 
							
								        if version == '1':
							 | 
						||
| 
								 | 
							
								            c.execute("update decks set lastSync = ?", [lastSync])
							 | 
						||
| 
								 | 
							
								        elif version == '2':
							 | 
						||
| 
								 | 
							
								            c.execute("update decks set syncName = ?, lastSync = ?",
							 | 
						||
| 
								 | 
							
								                      [checksum(path.encode("utf-8")), lastSync])
							 | 
						||
| 
								 | 
							
								        c.commit()
							 | 
						||
| 
								 | 
							
								        c.close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return lastSync
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _stuffedResp(self, data):
							 | 
						||
| 
								 | 
							
								        return Response(
							 | 
						||
| 
								 | 
							
								            status='200 OK',
							 | 
						||
| 
								 | 
							
								            content_type='application/json',
							 | 
						||
| 
								 | 
							
								            content_encoding='deflate',
							 | 
						||
| 
								 | 
							
								            body=data)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @wsgify
							 | 
						||
| 
								 | 
							
								    def __call__(self, req):
							 | 
						||
| 
								 | 
							
								        if req.path.startswith(self.base_url):
							 | 
						||
| 
								 | 
							
								            url = req.path[len(self.base_url):]
							 | 
						||
| 
								 | 
							
								            if url not in self.valid_urls:
							 | 
						||
| 
								 | 
							
								                raise HTTPNotFound()
							 | 
						||
| 
								 | 
							
								            
							 | 
						||
| 
								 | 
							
								            # get and check username and password
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                u = req.str_params.getone('u')
							 | 
						||
| 
								 | 
							
								                p = req.str_params.getone('p')
							 | 
						||
| 
								 | 
							
								            except KeyError:
							 | 
						||
| 
								 | 
							
								                raise HTTPBadRequest('Must pass username and password')
							 | 
						||
| 
								 | 
							
								            if not self.check_password(u, p):
							 | 
						||
| 
								 | 
							
								                #raise HTTPBadRequest('Incorrect username or password')
							 | 
						||
| 
								 | 
							
								                return self._stuffedResp(self._stuff({'status':'invalidUserPass'}))
							 | 
						||
| 
								 | 
							
								            dirname = self.username2dirname(u)
							 | 
						||
| 
								 | 
							
								            if dirname is None:
							 | 
						||
| 
								 | 
							
								                raise HTTPBadRequest('Incorrect username or password')
							 | 
						||
| 
								 | 
							
								            user_path = os.path.join(self.data_root, dirname)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # get and lock the (optional) deck for this request
							 | 
						||
| 
								 | 
							
								            d = None
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                d = unicode(req.str_params.getone('d'), 'utf-8')
							 | 
						||
| 
								 | 
							
								                # AnkiDesktop actually passes us the string value 'None'!
							 | 
						||
| 
								 | 
							
								                if d == 'None':
							 | 
						||
| 
								 | 
							
								                    d = None
							 | 
						||
| 
								 | 
							
								            except KeyError:
							 | 
						||
| 
								 | 
							
								                pass
							 | 
						||
| 
								 | 
							
								            if d is not None:
							 | 
						||
| 
								 | 
							
								                # get the full deck path name
							 | 
						||
| 
								 | 
							
								                d = os.path.abspath(os.path.join(user_path, d)+'.anki')
							 | 
						||
| 
								 | 
							
								                if d[:len(user_path)] != user_path:
							 | 
						||
| 
								 | 
							
								                    raise HTTPBadRequest('Bad deck name')
							 | 
						||
| 
								 | 
							
								                thread = AnkiServer.deck.thread_pool.start(d)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                thread = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if url == 'getDecks':
							 | 
						||
| 
								 | 
							
								                # force the version up to 1.2.x
							 | 
						||
| 
								 | 
							
								                v = req.str_params.getone('libanki')
							 | 
						||
| 
								 | 
							
								                if v.startswith('0.') or v.startswith('1.0'):
							 | 
						||
| 
								 | 
							
								                    return self._stuffedResp(self._stuff({'status':'oldVersion'}))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                # store the data the user passes us keyed with the username.  This
							 | 
						||
| 
								 | 
							
								                # will be used later by SyncAppHandler for version compatibility.
							 | 
						||
| 
								 | 
							
								                self.users[u] = makeArgs(req.str_params)
							 | 
						||
| 
								 | 
							
								                return self._stuffedResp(self._getDecks(user_path))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            elif url in SyncAppHandler.operations:
							 | 
						||
| 
								 | 
							
								                handler = SyncAppHandler()
							 | 
						||
| 
								 | 
							
								                func = getattr(handler, url)
							 | 
						||
| 
								 | 
							
								                args = makeArgs(req.str_params)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                if thread is not None:
							 | 
						||
| 
								 | 
							
								                    # If this is for a specific deck, then it needs to run
							 | 
						||
| 
								 | 
							
								                    # inside of the DeckThread.
							 | 
						||
| 
								 | 
							
								                    def runFunc(wrapper):
							 | 
						||
| 
								 | 
							
								                        handler.deck = wrapper.open()
							 | 
						||
| 
								 | 
							
								                        ret = func(**args)
							 | 
						||
| 
								 | 
							
								                        handler.deck.save()
							 | 
						||
| 
								 | 
							
								                        return ret
							 | 
						||
| 
								 | 
							
								                    runFunc.func_name = url
							 | 
						||
| 
								 | 
							
								                    ret = thread.execute(runFunc, [thread.wrapper])
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    # Otherwise, we can simply execute it in this thread.
							 | 
						||
| 
								 | 
							
								                    ret = func(**args)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                # clean-up user data stored in getDecks
							 | 
						||
| 
								 | 
							
								                if url == 'finish':
							 | 
						||
| 
								 | 
							
								                    del self.users[u]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                return self._stuffedResp(ret)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            elif url == 'fulldown':
							 | 
						||
| 
								 | 
							
								                # set the syncTime before we send it
							 | 
						||
| 
								 | 
							
								                def setupForSync(wrapper):
							 | 
						||
| 
								 | 
							
								                    wrapper.close()
							 | 
						||
| 
								 | 
							
								                    c = sqlite.connect(d)
							 | 
						||
| 
								 | 
							
								                    lastSync = time.time()
							 | 
						||
| 
								 | 
							
								                    c.execute("update decks set lastSync = ?", [lastSync])
							 | 
						||
| 
								 | 
							
								                    c.commit()
							 | 
						||
| 
								 | 
							
								                    c.close()
							 | 
						||
| 
								 | 
							
								                thread.execute(setupForSync, [thread.wrapper])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                return Response(status='200 OK', content_type='application/octet-stream', content_encoding='deflate', content_disposition='attachment; filename="'+os.path.basename(d).encode('utf-8')+'"', app_iter=FileIterable(d))
							 | 
						||
| 
								 | 
							
								            elif url == 'fullup':
							 | 
						||
| 
								 | 
							
								                #version = self.user_libanki_version(u)
							 | 
						||
| 
								 | 
							
								                try:
							 | 
						||
| 
								 | 
							
								                    version = req.str_params.getone('v')
							 | 
						||
| 
								 | 
							
								                except KeyError:
							 | 
						||
| 
								 | 
							
								                    version = '1'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                infile = req.str_params['deck'].file
							 | 
						||
| 
								 | 
							
								                lastSync = thread.execute(self._fullup, [thread.wrapper, infile, version])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                # append the 'lastSync' value for libanki 1.1 and 1.2
							 | 
						||
| 
								 | 
							
								                if version == '2':
							 | 
						||
| 
								 | 
							
								                    body = 'OK '+str(lastSync)
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    body = 'OK'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                return Response(status='200 OK', content_type='application/text', body=body)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return Response(status='200 OK', content_type='text/plain', body='Anki Server')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Our entry point
							 | 
						||
| 
								 | 
							
								def make_app(global_conf, **local_conf):
							 | 
						||
| 
								 | 
							
								    return SyncApp(**local_conf)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def main():
							 | 
						||
| 
								 | 
							
								    from wsgiref.simple_server import make_server
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ankiserver = DeckApp('.', '/sync/')
							 | 
						||
| 
								 | 
							
								    httpd = make_server('', 8001, ankiserver)
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        httpd.serve_forever()
							 | 
						||
| 
								 | 
							
								    except KeyboardInterrupt:
							 | 
						||
| 
								 | 
							
								        print "Exiting ..."
							 | 
						||
| 
								 | 
							
								    finally:
							 | 
						||
| 
								 | 
							
								        AnkiServer.deck.thread_pool.shutdown()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if __name__ == '__main__': main()
							 | 
						||
| 
								 | 
							
								
							 |