anki-sync-server/AnkiServer/collection.py
David Snopek e25cf25684 Squashed commit of the following:
commit cb509e8f75e3dcdbc66327be4bfbf6661aa084b5
Author: David Snopek <dsnopek@gmail.com>
Date:   Fri Jul 12 22:06:28 2013 +0100

    Cut down 'import' statements to only modules actually used.

commit 0ea255115e095e31af5a991e9cce2b5b15cb496d
Author: David Snopek <dsnopek@gmail.com>
Date:   Fri Jul 12 22:00:06 2013 +0100

     * Add getCollectionManager() so that the whole process can share the same ThreadingCollectionManager object.

     * Got the RestApp actually working!

commit 00997bab600b13d4b430ed2c2839b1d2232f55ed
Author: David Snopek <dsnopek@gmail.com>
Date:   Fri Jul 12 21:04:58 2013 +0100

    Got the sync_app working again (more or less)

commit 459c69566bb92d2c0195a384e067d98c059bdea7
Author: David Snopek <dsnopek@gmail.com>
Date:   Fri Jul 12 19:47:40 2013 +0100

    Started implementing test for the RESTful callbacks that PrepECN is going to need.

commit 7ffbac793f9bf45ab9056c1de475422b8742e107
Author: David Snopek <dsnopek@gmail.com>
Date:   Fri Jul 12 17:19:06 2013 +0100

    Started work on a WSGI app for RESTful access to Anki based on Bibliobird code here:

      https://raw.github.com/dsnopek/bbcom/master/AnkiServer/AnkiServer/deck.py

commit 8820411388ce0c2b7b14769c614c22c675d2dbdd
Author: David Snopek <dsnopek@gmail.com>
Date:   Fri Jul 12 15:03:56 2013 +0100

     * Seperated the collection and threading code.

     * Implemented a new interface to interact with the collections, which will hopefully be more transparent and testable.
2013-07-12 22:08:16 +01:00

110 lines
3.4 KiB
Python

import anki
import anki.storage
import os, errno
__all__ = ['CollectionWrapper', 'CollectionManager']
class CollectionWrapper(object):
"""A simple wrapper around an anki.storage.Collection object.
This allows us to manage and refer to the collection, whether it's open or not. It
also provides a special "continuation passing" interface for executing functions
on the collection, which makes it easy to switch to a threading mode.
See ThreadingCollectionWrapper for a version that maintains a seperate thread for
interacting with the collection.
"""
def __init__(self, path, setup_new_collection=None):
self.path = os.path.realpath(path)
self.setup_new_collection = setup_new_collection
self.__col = None
def execute(self, func, args=[], kw={}, waitForReturn=True):
""" Executes the given function with the underlying anki.storage.Collection
object as the first argument and any additional arguments specified by *args
and **kw.
If 'waitForReturn' is True, then it will block until the function has
executed and return its return value. If False, the function MAY be
executed some time later and None will be returned.
"""
# Open the collection and execute the function
self.open()
args = [self.__col] + args
ret = func(*args, **kw)
# Only return the value if they requested it, so the interface remains
# identical between this class and ThreadingCollectionWrapper
if waitForReturn:
return ret
def __create_collection(self):
"""Creates a new collection and runs any special setup."""
# mkdir -p the path, because it might not exist
dirname = os.path.dirname(self.path)
try:
os.makedirs(dirname)
except OSError, exc:
if exc.errno == errno.EEXIST:
pass
else:
raise
self.__col = ank.storage.Collection(self.path)
# Do any special setup
if self.setup_new_collection is not None:
self.setup_new_collection(self.__col)
def open(self):
"""Open the collection, or create it if it doesn't exist."""
if self.__col is None:
if os.path.exists(self.path):
self.__col = anki.storage.Collection(self.path)
else:
self.__col = self.__create_collection()
def close(self):
"""Close the collection if opened."""
if not self.opened():
return
self.__col.close()
self.__col = None
def opened(self):
"""Returns True if the collection is open, False otherwise."""
return self.__col is not None
class CollectionManager(object):
"""Manages a set of CollectionWrapper objects."""
collection_wrapper = CollectionWrapper
def __init__(self):
self.collections = {}
def get_collection(self, path, setup_new_collection=None):
"""Gets a CollectionWrapper for the given path."""
path = os.path.realpath(path)
try:
col = self.collections[path]
except KeyError:
col = self.collections[path] = self.collection_wrapper(path, setup_new_collection)
return col
def shutdown(self):
"""Close all CollectionWrappers managed by this object."""
for path, col in self.collections.items():
del self.collections[path]
col.close()