Use list.sort's keyword parameters
[joel/kofoto.git] / src / test / shelftests.py
index ed06372..4c77fa5 100755 (executable)
@@ -2,16 +2,45 @@
 
 import gc
 import os
+import shutil
 import sys
+import threading
 import unittest
 
 if __name__ == "__main__":
     cwd = os.getcwd()
     libdir = unicode(os.path.realpath(
-        os.path.join(os.path.dirname(sys.argv[0]), "..", "lib")))
+        os.path.join(os.path.dirname(sys.argv[0]), "..", "packages")))
     os.chdir(libdir)
     sys.path.insert(0, libdir)
-from kofoto.shelf import *
+from kofoto.shelf import \
+    Shelf, \
+    computeImageHash, \
+    makeValidTag, \
+    verifyValidAlbumTag, \
+    verifyValidCategoryTag
+from kofoto.albumtype import AlbumType
+from kofoto.imageversiontype import ImageVersionType
+from kofoto.shelfexceptions import \
+    AlbumDoesNotExistError, \
+    AlbumExistsError, \
+    BadAlbumTagError, \
+    BadCategoryTagError, \
+    CategoriesAlreadyConnectedError, \
+    CategoryDoesNotExistError, \
+    CategoryExistsError, \
+    CategoryLoopError, \
+    CategoryPresentError, \
+    FailedWritingError, \
+    ImageDoesNotExistError, \
+    ImageVersionDoesNotExistError, \
+    ImageVersionExistsError, \
+    ShelfLockedError, \
+    ShelfNotFoundError, \
+    UndeletableAlbumError, \
+    UndeletableAlbumError, \
+    UnsettableChildrenError, \
+    UnsupportedShelfError
 
 PICDIR = unicode(os.path.realpath(
     os.path.join("..", "reference_pictures", "working")))
@@ -19,13 +48,28 @@ PICDIR = unicode(os.path.realpath(
 ######################################################################
 
 db = "shelf.tmp"
-codeset = "latin1"
 
 def removeTmpDb():
     for x in [db, db + "-journal"]:
         if os.path.exists(x):
             os.unlink(x)
 
+class LockerThread(threading.Thread):
+    def __init__(self, mContinue, ltContinue):
+        threading.Thread.__init__(self)
+        self.mContinue = mContinue
+        self.ltContinue = ltContinue
+
+    def run(self):
+        s = Shelf(db)
+        s.create()
+        s.begin()
+        self.mContinue.set()
+        self.ltContinue.wait()
+        s.rollback()
+
+######################################################################
+
 class TestPublicShelfFunctions(unittest.TestCase):
     def test_computeImageHash(self):
         s = computeImageHash(os.path.join(PICDIR, "arlaharen.png"))
@@ -84,67 +128,82 @@ class TestNegativeShelfOpens(unittest.TestCase):
         removeTmpDb()
 
     def test_NonexistingShelf(self):
-        for params, kwparams in [((db, codeset), {}),
-                                 ((db, codeset, False), {}),
-                                 ((db, codeset), {"create": False})]:
-            try:
-                Shelf(*params, **kwparams)
-            except ShelfNotFoundError:
-                pass
-            else:
-                assert False, (params, kwparams)
-            assert not os.path.exists(db)
+        try:
+            s = Shelf(db)
+            s.begin()
+        except ShelfNotFoundError:
+            pass
+        else:
+            assert False
+        assert not os.path.exists(db)
 
     def test_BadShelf(self):
         file(db, "w") # Create empty file.
         try:
-            Shelf(db, codeset)
+            s = Shelf(db)
+            s.begin()
         except UnsupportedShelfError:
             pass
         else:
             assert False
 
+    def test_LockedShelf(self):
+        mContinue = threading.Event()
+        ltContinue = threading.Event()
+        lt = LockerThread(mContinue, ltContinue)
+        lt.start()
+        mContinue.wait()
+        try:
+            try:
+                s = Shelf(db)
+                s.begin()
+            except ShelfLockedError:
+                pass
+            else:
+                assert False
+        finally:
+            ltContinue.set()
+            lt.join()
+
 class TestShelfCreation(unittest.TestCase):
     def tearDown(self):
         removeTmpDb()
 
     def test_CreateShelf1(self):
-        assert Shelf(db, codeset, True)
-        assert os.path.exists(db)
-
-    def test_CreateShelf2(self):
-        assert Shelf(db, codeset, create=True)
-        assert os.path.exists(db)
-
-class TestShelfOpen(unittest.TestCase):
-    def tearDown(self):
-        removeTmpDb()
-
-    def test_CreateShelf(self):
-        assert Shelf(db, codeset, True)
+        s = Shelf(db)
+        s.create()
         assert os.path.exists(db)
 
     def test_CreateShelf2(self):
-        assert Shelf(db, codeset, create=True)
-        assert os.path.exists(db)
+        file(db, "w") # Create empty file.
+        s = Shelf(db)
+        try:
+            s.create()
+        except FailedWritingError:
+            pass
+        else:
+            assert False
 
 class TestShelfMemoryLeakage(unittest.TestCase):
     def tearDown(self):
         removeTmpDb()
 
     def test_MemoryLeak1(self):
-        Shelf(db, codeset, True)
+        s = Shelf(db)
+        s.create()
         assert gc.collect() == 0
 
     def test_MemoryLeak2(self):
-        s = Shelf(db, codeset, True)
+        s = Shelf(db)
+        s.create()
         s.begin()
         s.getObject(0)
         s.rollback()
         assert gc.collect() == 0
 
     def test_MemoryLeak3(self):
-        s = Shelf(db, codeset, True)
+        s = Shelf(db)
+        s.create()
         s.begin()
         s.getObject(0)
         s.commit()
@@ -155,31 +214,34 @@ class TestShelfTransactions(unittest.TestCase):
         removeTmpDb()
 
     def test_commit(self):
-        s = Shelf(db, codeset, True)
+        s = Shelf(db)
+        s.create()
         s.begin()
         s.createAlbum(u"foo")
-        assert s.getAlbum(u"foo")
+        assert s.getAlbumByTag(u"foo")
         s.commit()
-        s = Shelf(db, codeset)
+        s = Shelf(db)
         s.begin()
-        assert s.getAlbum(u"foo")
+        assert s.getAlbumByTag(u"foo")
         s.rollback()
 
     def test_rollback(self):
-        s = Shelf(db, codeset, True)
+        s = Shelf(db)
+        s.create()
         s.begin()
         s.createAlbum(u"foo")
         s.rollback()
         s.begin()
         try:
-            s.getAlbum(u"foo")
+            s.getAlbumByTag(u"foo")
         except AlbumDoesNotExistError:
             pass
         else:
             assert False
 
     def test_isModified(self):
-        s = Shelf(db, codeset, True)
+        s = Shelf(db)
+        s.create()
         s.begin()
         assert not s.isModified()
         s.createAlbum(u"foo")
@@ -193,7 +255,8 @@ class TestShelfTransactions(unittest.TestCase):
         res = [False]
         def f(x):
             res[0] = True
-        s = Shelf(db, codeset, True)
+        s = Shelf(db)
+        s.create()
         s.begin()
         s.registerModificationCallback(f)
         assert not res[0]
@@ -210,26 +273,29 @@ class TestShelfTransactions(unittest.TestCase):
 
 class TestShelfFixture(unittest.TestCase):
     def setUp(self):
-        self.shelf = Shelf(db, codeset, True)
+        self.shelf = Shelf(db)
+        self.shelf.create()
         self.shelf.begin()
         root = self.shelf.getRootAlbum()
         alpha = self.shelf.createAlbum(u"alpha")
         beta = self.shelf.createAlbum(u"beta")
-        children = [alpha, beta]
+        children = [beta]
         for x in os.listdir(PICDIR):
             loc = os.path.join(PICDIR, x)
             if not os.path.isfile(loc):
                 continue
-            children.append(self.shelf.createImage(loc))
+            image = self.shelf.createImage()
+            imageversion = self.shelf.createImageVersion(
+                image, loc, ImageVersionType.Original)
+            children.append(image)
         del children[-1] # The last image becomes orphaned.
-        alpha.setChildren(children) # This creates a cycle.
+        alpha.setChildren(children)
         beta.setChildren(list(beta.getChildren()) + [children[-1]])
-        root.setChildren(list(root.getChildren()) + [
-            alpha,
-            beta,
-            self.shelf.createAlbum(u"gamma", u"allalbums"),
-            self.shelf.createAlbum(u"delta", u"allimages")])
-        self.shelf.createAlbum(u"epsilon", u"plain") # Orphan album.
+        root.setChildren(list(root.getChildren()) + [alpha, beta])
+        self.shelf.createAlbum(u"epsilon", AlbumType.Plain) # Orphaned album.
+        zeta = self.shelf.createAlbum(u"zeta", AlbumType.Search)
+        zeta.setAttribute(u"query", u"a")
+        root.setChildren(list(root.getChildren()) + [zeta])
 
         cat_a = self.shelf.createCategory(u"a", u"A")
         cat_b = self.shelf.createCategory(u"b", u"B")
@@ -240,9 +306,10 @@ class TestShelfFixture(unittest.TestCase):
         cat_b.connectChild(cat_d)
         cat_c.connectChild(cat_d)
 
+        self.shelf.flushObjectCache()
+        self.shelf.flushCategoryCache()
+
     def tearDown(self):
-        # Break cycle mentioned in setUp above.
-        self.shelf.getAlbum(u"alpha").setChildren([])
         self.shelf.rollback()
         removeTmpDb()
 
@@ -253,26 +320,24 @@ class TestShelfMethods(TestShelfFixture):
 
     def test_getStatistics(self):
         s = self.shelf.getStatistics()
-        assert s["nalbums"] == 7
+        assert s["nalbums"] == 6
         assert s["nimages"] == 11
+        assert s["nimageversions"] == 11
 
     def test_createdObjects(self):
         root = self.shelf.getRootAlbum()
         children = list(root.getChildren())
-        assert len(children) == 5
-        orphans, alpha, beta, gamma, delta = children
-        assert self.shelf.getObject(u"alpha") == alpha
-        assert self.shelf.getAlbum(u"beta") == beta
-        assert len(list(alpha.getChildren())) == 12
+        assert len(children) == 4
+        orphans, alpha, beta, zeta = children
+        assert self.shelf.getAlbum(alpha.getId()) == alpha
+        assert self.shelf.getAlbumByTag(u"beta") == beta
+        assert len(list(alpha.getChildren())) == 11
         assert len(list(beta.getChildren())) == 1
-        assert len(list(gamma.getChildren())) == 7
-        assert len(list(delta.getChildren())) == 11
 
     def test_createdAttributes(self):
-        for image in self.shelf.getAllImages():
-            assert image.getAttribute(u"registered")
-        image = self.shelf.getImage(
+        imageversion = self.shelf.getImageVersionByLocation(
             os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
+        image = imageversion.getImage()
         assert image.getAttribute(u"captured") == "2002-02-02 22:20:51"
         assert image.getAttribute(u"cameramake") == "Canon"
         assert image.getAttribute(u"cameramodel") == "Canon DIGITAL IXUS"
@@ -286,13 +351,24 @@ class TestShelfMethods(TestShelfFixture):
             assert False
 
     def test_getAlbum(self):
-        album = self.shelf.getAlbum(u"alpha")
-        album = self.shelf.getAlbum(album.getTag())
-        album = self.shelf.getAlbum(album.getId())
+        album = self.shelf.getAlbum(0)
+        assert album == self.shelf.getRootAlbum()
 
     def test_negativeGetAlbum(self):
         try:
-            self.shelf.getAlbum(u"nonexisting")
+            self.shelf.getAlbum(12345678)
+        except AlbumDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_getAlbumByTag(self):
+        album = self.shelf.getAlbumByTag(u"root")
+        assert album == self.shelf.getRootAlbum()
+
+    def test_negativeGetAlbumByTag(self):
+        try:
+            self.shelf.getAlbum(u"12345678")
         except AlbumDoesNotExistError:
             pass
         else:
@@ -300,28 +376,39 @@ class TestShelfMethods(TestShelfFixture):
 
     def test_getRootAlbum(self):
         root = self.shelf.getRootAlbum()
-        assert root == self.shelf.getAlbum(u"root")
+        assert root.getId() == 0
+        assert root == self.shelf.getAlbumByTag(u"root")
 
     def test_getAllAlbums(self):
         albums = list(self.shelf.getAllAlbums())
-        assert len(albums) == 7
+        assert len(albums) == 6
+
+    def test_getAllImageVersions(self):
+        imageversions = list(self.shelf.getAllImageVersions())
+        assert len(imageversions) == 11
 
-    def test_getAllImages(self):
-        images = list(self.shelf.getAllImages())
-        assert len(images) == 11
+    def test_getImageVersionsInDirectory(self):
+        self.shelf.flushImageVersionCache()
 
-    def test_getImagesInDirectory(self):
-        images = list(self.shelf.getImagesInDirectory(u"."))
-        assert len(images) == 0
-        images = list(self.shelf.getImagesInDirectory(PICDIR))
-        assert len(images) == 11
+        imageversions = list(self.shelf.getImageVersionsInDirectory(u"."))
+        assert len(imageversions) == 0
+
+        # Image versions not in cache.
+        imageversions = list(self.shelf.getImageVersionsInDirectory(PICDIR))
+        assert len(imageversions) == 11
+
+        # Image versions in cache.
+        imageversions = list(self.shelf.getImageVersionsInDirectory(PICDIR))
+        assert len(imageversions) == 11
 
     def test_deleteAlbum(self):
-        self.shelf.deleteAlbum(u"beta")
+        album = self.shelf.getAlbumByTag(u"beta")
+        self.shelf.deleteAlbum(album.getId())
 
     def test_negativeRootAlbumDeletion(self):
+        root = self.shelf.getRootAlbum()
         try:
-            self.shelf.deleteAlbum(u"root")
+            self.shelf.deleteAlbum(root.getId())
         except UndeletableAlbumError:
             pass
         else:
@@ -329,7 +416,7 @@ class TestShelfMethods(TestShelfFixture):
 
     def test_negativeAlbumDeletion(self):
         try:
-            self.shelf.deleteAlbum(u"nonexisting")
+            self.shelf.deleteAlbum(12345678)
         except AlbumDoesNotExistError:
             pass
         else:
@@ -337,55 +424,152 @@ class TestShelfMethods(TestShelfFixture):
 
     def test_negativeImageCreation(self):
         try:
-            self.shelf.createImage(os.path.join(PICDIR, "arlaharen.png"))
-        except ImageExistsError:
+            image = self.shelf.createImage()
+            self.shelf.createImageVersion(
+                image,
+                os.path.join(PICDIR, "arlaharen.png"),
+                ImageVersionType.Original)
+        except ImageVersionExistsError:
             pass
         else:
             assert False
 
     def test_getImage(self):
-        image = self.shelf.getImage(os.path.join(PICDIR, "arlaharen.png"))
-        image = self.shelf.getImage(image.getHash())
-        image = self.shelf.getImage(image.getId())
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        image = imageversion.getImage()
+        assert self.shelf.getImage(image.getId()) == image
 
     def test_negativeGetImage(self):
         try:
-            self.shelf.getImage(u"nonexisting")
+            self.shelf.getImage(12345678)
         except ImageDoesNotExistError:
             pass
         else:
             assert False
 
+    def test_getImageVersion(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert self.shelf.getImageVersion(imageversion.getId()) == imageversion
+
+    def test_negativeGetImageVersion(self):
+        try:
+            self.shelf.getImageVersion(12345678)
+        except ImageVersionDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_getImageVersionByHash(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert self.shelf.getImageVersionByHash(
+            imageversion.getHash()) == imageversion
+
+    def test_negativeGetImageVersionByHash(self):
+        try:
+            self.shelf.getImageVersion(u"badhash")
+        except ImageVersionDoesNotExistError:
+            pass
+        else:
+            assert False
+
+    def test_getImageVersionByLocation(self):
+        imageversion1 = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, u"arlaharen.png"))
+        currentDir = os.getcwd()
+        try:
+            os.chdir(PICDIR)
+            imageversion2 = self.shelf.getImageVersionByLocation(
+                u"arlaharen.png")
+        finally:
+            os.chdir(currentDir)
+        assert imageversion1 == imageversion2
+
+    def test_negativeGetImageVersionByHash(self):
+        try:
+            self.shelf.getImageVersionByLocation(u"/bad/location")
+        except ImageVersionDoesNotExistError:
+            pass
+        else:
+            assert False
+
     def test_deleteImage(self):
-        self.shelf.deleteImage(os.path.join(PICDIR, "arlaharen.png"))
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        imageid = imageversion.getImage().getId()
+        self.shelf.deleteImage(imageid)
+        try:
+            self.shelf.getImageVersionByLocation(
+                os.path.join(PICDIR, "arlaharen.png"))
+        except ImageVersionDoesNotExistError:
+            pass
+        else:
+            assert False
 
     def test_negativeImageDeletion(self):
         try:
-            self.shelf.deleteImage(u"nonexisting")
+            self.shelf.deleteImage(12345678)
         except ImageDoesNotExistError:
             pass
         else:
             assert False
 
+    def test_deleteImageVersion(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        self.shelf.deleteImageVersion(imageversion.getId())
+
+    def test_negativeImageVersionDeletion(self):
+        try:
+            self.shelf.deleteImageVersion(12345678)
+        except ImageVersionDoesNotExistError:
+            pass
+        else:
+            assert False
+
     def test_getObject(self):
-        album = self.shelf.getObject(u"alpha")
-        album = self.shelf.getObject(album.getTag())
-        album = self.shelf.getObject(album.getId())
-        image = self.shelf.getObject(os.path.join(PICDIR, "arlaharen.png"))
-        image = self.shelf.getObject(image.getHash())
-        image = self.shelf.getObject(image.getId())
+        rootalbum = self.shelf.getRootAlbum()
+        album = self.shelf.getObject(rootalbum.getId())
+        assert album == rootalbum
 
     def test_deleteObject(self):
-        self.shelf.deleteObject(u"beta")
-        self.shelf.deleteObject(os.path.join(PICDIR, "arlaharen.png"))
+        albumid = self.shelf.getAlbumByTag(u"beta").getId()
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        imageid = imageversion.getImage().getId()
+        self.shelf.deleteObject(albumid)
+        self.shelf.deleteObject(imageid)
+        try:
+            self.shelf.getAlbum(albumid)
+        except AlbumDoesNotExistError:
+            pass
+        else:
+            assert False
+        try:
+            self.shelf.getImage(imageid)
+        except ImageDoesNotExistError:
+            pass
+        else:
+            assert False
 
     def test_getAllAttributeNames(self):
-        attrnames = list(self.shelf.getAllAttributeNames())
-        attrnames.sort()
+        attrnames = sorted(self.shelf.getAllAttributeNames())
         assert attrnames == [
-            "cameramake", "cameramodel", "captured", "description", "height",
-            "orientation", "registered", "title", "width"
-            ]
+            "cameramake", "cameramodel", "captured", "description",
+            "digitalzoom", "exposurebias", "exposureprogram", "exposuretime",
+            "flash", "fnumber", "focallength", "iso", "orientation", "query",
+            "title"
+            ], attrnames
+
+    def test_getCategory(self):
+        category = self.shelf.getCategory(1)
+        assert category.getId() == 1
+
+    def test_getCategoryByTag(self):
+        category = self.shelf.getCategoryByTag(u"a")
+        assert category.getTag() == u"a"
 
     def test_negativeCreateCategory(self):
         try:
@@ -396,39 +580,43 @@ class TestShelfMethods(TestShelfFixture):
             assert False
 
     def test_deleteCategory(self):
-        self.shelf.deleteCategory(u"a")
+        category = self.shelf.getCategoryByTag(u"a")
+        self.shelf.deleteCategory(category.getId())
 
     def test_negativeDeleteCategory(self):
         try:
-            self.shelf.deleteCategory(u"nonexisting")
+            self.shelf.deleteCategory(12345678)
         except CategoryDoesNotExistError:
             pass
         else:
             assert False
 
     def test_getRootCategories(self):
-        categories = list(self.shelf.getRootCategories())
-        cat_a = self.shelf.getCategory(u"a")
-        assert categories == [cat_a]
+        categories = sorted(
+            self.shelf.getRootCategories(), key=lambda x: x.getTag())
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_events = self.shelf.getCategoryByTag(u"events")
+        cat_locations = self.shelf.getCategoryByTag(u"locations")
+        cat_people = self.shelf.getCategoryByTag(u"people")
+        assert categories == [cat_a, cat_events, cat_locations, cat_people], \
+               categories
 
 class TestCategory(TestShelfFixture):
     def test_categoryMethods(self):
-        cat_a = self.shelf.getCategory(u"a")
-        cat_b = self.shelf.getCategory(u"b")
-        cat_c = self.shelf.getCategory(u"c")
-        cat_d = self.shelf.getCategory(u"d")
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_b = self.shelf.getCategoryByTag(u"b")
+        cat_c = self.shelf.getCategoryByTag(u"c")
+        cat_d = self.shelf.getCategoryByTag(u"d")
 
-        assert self.shelf.getCategory(cat_a.getTag()) == cat_a
         assert self.shelf.getCategory(cat_a.getId()) == cat_a
         cat_a.setTag(u"foo")
-        assert self.shelf.getCategory(u"foo") == cat_a
+        assert self.shelf.getCategoryByTag(u"foo") == cat_a
 
         assert cat_a.getDescription() == "A"
         cat_a.setDescription(u"foo")
         assert cat_a.getDescription() == "foo"
 
-        a_children = list(cat_a.getChildren())
-        a_children.sort(lambda x, y: cmp(x.getId(), y.getId()))
+        a_children = sorted(cat_a.getChildren(), key=lambda x: x.getId())
         assert a_children == [cat_b, cat_c]
         b_children = list(cat_b.getChildren())
         assert b_children == [cat_d]
@@ -439,8 +627,7 @@ class TestCategory(TestShelfFixture):
         assert a_parents == []
         b_parents = list(cat_b.getParents())
         assert b_parents == [cat_a]
-        d_parents = list(cat_d.getParents())
-        d_parents.sort(lambda x, y: cmp(x.getTag(), y.getTag()))
+        d_parents = sorted(cat_d.getParents(), key=lambda x: x.getTag())
         assert d_parents == [cat_b, cat_c]
 
         assert not cat_a.isChildOf(cat_a)
@@ -462,8 +649,8 @@ class TestCategory(TestShelfFixture):
         assert cat_a.isParentOf(cat_d, recursive=True)
 
     def test_negativeCategoryConnectChild(self):
-        cat_a = self.shelf.getCategory(u"a")
-        cat_b = self.shelf.getCategory(u"b")
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_b = self.shelf.getCategoryByTag(u"b")
         try:
             cat_a.connectChild(cat_b)
         except CategoriesAlreadyConnectedError:
@@ -478,45 +665,45 @@ class TestCategory(TestShelfFixture):
             assert False
 
     def test_categoryDisconnectChild(self):
-        cat_a = self.shelf.getCategory(u"a")
-        cat_b = self.shelf.getCategory(u"b")
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_b = self.shelf.getCategoryByTag(u"b")
         cat_a.disconnectChild(cat_b)
         assert not cat_a.isParentOf(cat_b)
 
     def test_negativeCategoryDisconnectChild(self):
-        cat_a = self.shelf.getCategory(u"a")
-        cat_d = self.shelf.getCategory(u"d")
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_d = self.shelf.getCategoryByTag(u"d")
         cat_a.disconnectChild(cat_d) # No exception.
 
 class TestObject(TestShelfFixture):
     def test_getParents(self):
         root = self.shelf.getRootAlbum()
-        alpha = self.shelf.getAlbum(u"alpha")
-        beta = self.shelf.getAlbum(u"beta")
-        parents = list(beta.getParents())
-        parents.sort(lambda x, y: cmp(x.getTag(), y.getTag()))
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        beta = self.shelf.getAlbumByTag(u"beta")
+        parents = sorted(beta.getParents(), key=lambda x: x.getTag())
         assert parents == [alpha, root]
 
     def test_getAttribute(self):
-        orphans = self.shelf.getAlbum(u"orphans")
+        orphans = self.shelf.getAlbumByTag(u"orphans")
         assert orphans.getAttribute(u"title")
         assert orphans.getAttribute(u"description")
+        orphans.getAttributeMap() # Just populate the cache.
         assert not orphans.getAttribute(u"nonexisting")
+        assert u"nonexisting" not in orphans.getAttributeMap()
 
     def test_getAttributeMap(self):
-        orphans = self.shelf.getAlbum(u"orphans")
+        orphans = self.shelf.getAlbumByTag(u"orphans")
         map = orphans.getAttributeMap()
         assert "description" in map
         assert "title" in map
 
     def test_getAttributeNames(self):
-        orphans = self.shelf.getAlbum(u"orphans")
-        names = list(orphans.getAttributeNames())
-        names.sort()
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        names = sorted(orphans.getAttributeNames())
         assert names == ["description", "title"]
 
     def test_setAttribute(self):
-        orphans = self.shelf.getAlbum(u"orphans")
+        orphans = self.shelf.getAlbumByTag(u"orphans")
         orphans.setAttribute(u"foo", u"fie") # New.
         assert orphans.getAttribute(u"foo") == u"fie"
         assert u"foo" in orphans.getAttributeMap()
@@ -528,15 +715,17 @@ class TestObject(TestShelfFixture):
         assert u"foo" in orphans.getAttributeNames()
 
     def test_deleteAttribute(self):
-        orphans = self.shelf.getAlbum(u"orphans")
+        orphans = self.shelf.getAlbumByTag(u"orphans")
         orphans.deleteAttribute(u"nonexisting") # No exception.
         assert orphans.getAttribute(u"title")
         orphans.deleteAttribute(u"title")
+        orphans.getAttributeMap() # Just populate the cache.
         assert not orphans.getAttribute(u"title")
+        assert u"title" not in orphans.getAttributeMap()
 
     def test_addCategory(self):
-        orphans = self.shelf.getAlbum(u"orphans")
-        cat_a = self.shelf.getCategory(u"a")
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        cat_a = self.shelf.getCategoryByTag(u"a")
         assert list(orphans.getCategories()) == []
         orphans.addCategory(cat_a)
         assert list(orphans.getCategories()) == [cat_a]
@@ -549,58 +738,78 @@ class TestObject(TestShelfFixture):
         assert list(orphans.getCategories()) == [cat_a]
 
     def test_removeCategory(self):
-        orphans = self.shelf.getAlbum(u"orphans")
+        orphans = self.shelf.getAlbumByTag(u"orphans")
         assert list(orphans.getCategories()) == []
-        cat_a = self.shelf.getCategory(u"a")
+        cat_a = self.shelf.getCategoryByTag(u"a")
         orphans.addCategory(cat_a)
         assert list(orphans.getCategories()) == [cat_a]
         orphans.removeCategory(cat_a)
         assert list(orphans.getCategories()) == []
 
+    def test_deleteCategoryInvalidatesCategoryCache(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        assert list(orphans.getCategories()) == []
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        orphans.addCategory(cat_a)
+        assert list(orphans.getCategories()) == [cat_a]
+        self.shelf.deleteCategory(cat_a.getId())
+        assert list(orphans.getCategories()) == []
+
 class TestAlbum(TestShelfFixture):
     def test_getType(self):
-        alpha = self.shelf.getAlbum(u"alpha")
-        assert alpha.getType()
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        assert alpha.getType() == AlbumType.Plain
+
+    def test_isMutable(self):
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        assert alpha.isMutable()
 
     def test_getTag(self):
-        alpha = self.shelf.getAlbum(u"alpha")
+        alpha = self.shelf.getAlbumByTag(u"alpha")
         assert alpha.getTag() == u"alpha"
 
     def test_setTag(self):
-        alpha = self.shelf.getAlbum(u"alpha")
+        alpha = self.shelf.getAlbumByTag(u"alpha")
         alpha.setTag(u"alfa")
         assert alpha.getTag() == u"alfa"
 
     def test_getAlbumParents(self):
         root = self.shelf.getRootAlbum()
-        alpha = self.shelf.getAlbum(u"alpha")
-        parents = list(alpha.getAlbumParents())
-        parents.sort(lambda x, y: cmp(x.getTag(), y.getTag()))
-        assert parents == [alpha, root]
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        parents = sorted(alpha.getAlbumParents(), key=lambda x: x.getTag())
+        assert parents == [root]
 
     def test_isAlbum(self):
         assert self.shelf.getRootAlbum().isAlbum()
 
 class TestPlainAlbum(TestShelfFixture):
+    def test_getType(self):
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        assert alpha.getType() == AlbumType.Plain
+
+    def test_isMutable(self):
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        assert alpha.isMutable()
+
     def test_getChildren(self):
-        epsilon = self.shelf.getAlbum(u"epsilon")
-        alpha = self.shelf.getAlbum(u"alpha")
-        beta = self.shelf.getAlbum(u"beta")
+        epsilon = self.shelf.getAlbumByTag(u"epsilon")
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        beta = self.shelf.getAlbumByTag(u"beta")
         assert list(epsilon.getChildren()) == []
         alphaChildren = list(alpha.getChildren())
         assert list(beta.getChildren()) == [alphaChildren[-1]]
 
     def test_getAlbumChildren(self):
-        alpha = self.shelf.getAlbum(u"alpha")
-        beta = self.shelf.getAlbum(u"beta")
-        epsilon = self.shelf.getAlbum(u"epsilon")
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        beta = self.shelf.getAlbumByTag(u"beta")
+        epsilon = self.shelf.getAlbumByTag(u"epsilon")
         alphaAlbumChildren = list(alpha.getAlbumChildren())
-        assert alphaAlbumChildren == [alpha, beta]
+        assert alphaAlbumChildren == [beta]
         assert list(epsilon.getAlbumChildren()) == []
 
     def test_setChildren(self):
         root = self.shelf.getRootAlbum()
-        beta = self.shelf.getAlbum(u"beta")
+        beta = self.shelf.getAlbumByTag(u"beta")
         assert list(beta.getChildren()) != []
         beta.setChildren([beta, root])
         assert list(beta.getChildren()) == [beta, root]
@@ -608,105 +817,210 @@ class TestPlainAlbum(TestShelfFixture):
         assert list(beta.getChildren()) == []
 
 class TestImage(TestShelfFixture):
+    def test_isAlbum(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert not imageversion.getImage().isAlbum()
+
+    def test_getImageVersions(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        image = imageversion.getImage()
+        assert list(image.getImageVersions()) == [imageversion]
+        imageversion2 = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
+        imageversion2.setImage(image)
+        imageversions = set(image.getImageVersions())
+        assert set(image.getImageVersions()) == imageversions
+        self.shelf.deleteImageVersion(imageversion.getId())
+        self.shelf.deleteImageVersion(imageversion2.getId())
+        assert list(image.getImageVersions()) == []
+
+    def test_getPrimaryVersion(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        image = imageversion.getImage()
+        assert image.getPrimaryVersion() == imageversion
+        imageversion2 = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
+        imageversion2.setImage(image)
+        assert image.getPrimaryVersion() == imageversion
+        imageversion2.makePrimary()
+        assert image.getPrimaryVersion() == imageversion2
+
+        newImage = self.shelf.createImage()
+        lastImageVersion = list(image.getImageVersions())[-1]
+        lastImageVersion.setImage(newImage)
+        assert image.getPrimaryVersion() != lastImageVersion
+        assert newImage.getPrimaryVersion() == lastImageVersion
+        lastImageVersion.setImage(image)
+
+        self.shelf.deleteImageVersion(imageversion2.getId())
+        assert image.getPrimaryVersion() == imageversion
+        self.shelf.deleteImageVersion(imageversion.getId())
+        assert image.getPrimaryVersion() == None
+
+class TestImageVersion(TestShelfFixture):
+    def test_getType(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert imageversion.getType() == ImageVersionType.Original
+        imageversion.setType(ImageVersionType.Important)
+        assert imageversion.getType() == ImageVersionType.Important
+        imageversion.setType(ImageVersionType.Other)
+        assert imageversion.getType() == ImageVersionType.Other
+        imageversion.setType(ImageVersionType.Original)
+        assert imageversion.getType() == ImageVersionType.Original
+
+    # ImageVersion.makePrimary tested in TestImage.test_getPrimaryVersion.
+    # ImageVersion.setImage tested in TestImage.test_getPrimaryVersion.
+    # ImageVersion.setType tested in TestImageVersion.test_getType.
+    # ImageVersion.setComment tested in TestImageVersion.test_getComment.
+
+    def test_getComment(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert imageversion.getComment() == ""
+        imageversion.setComment(u"a comment")
+        assert imageversion.getComment() == u"a comment"
+
+    def test_getHash(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert imageversion.getHash() == "39a1266d2689f53d48b09a5e0ca0af1f"
+
     def test_getLocation(self):
         location = os.path.join(PICDIR, "arlaharen.png")
-        image = self.shelf.getImage(location)
-        assert image.getLocation() == os.path.realpath(location)
+        imageversion = self.shelf.getImageVersionByLocation(location)
+        assert imageversion.getLocation() == os.path.realpath(location)
+
+    def test_getModificationTime(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        t = imageversion.getModificationTime()
+        assert isinstance(t, int)
+        assert t > 0
+
+    def test_getSize(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        assert imageversion.getSize() == (304, 540)
+
+    def test_contentChanged(self):
+        path = os.path.join(PICDIR, "arlaharen.png")
+        imageversion = self.shelf.getImageVersionByLocation(path)
+        self.shelf.deleteImageVersion(imageversion.getId())
+        newpath = u"tmp.png"
+        try:
+            shutil.copy2(path, newpath)
+            newimage = self.shelf.createImage()
+            newimageversion = self.shelf.createImageVersion(
+                newimage, newpath, ImageVersionType.Original)
+            oldmtime = imageversion.getModificationTime()
+            f = open(newpath, "a")
+            f.write("foo")
+            f.close()
+            newimageversion.contentChanged()
+            assert newimageversion.getHash() == "b27312d9739c0edfd115f824be244b75"
+            assert newimageversion.getModificationTime() > oldmtime
+        finally:
+            try:
+                os.unlink(newpath)
+            except OSError:
+                pass
 
-    def test_setLocation(self):
+    def test_locationChanged(self):
         location = os.path.join(PICDIR, "arlaharen.png")
-        image = self.shelf.getImage(location)
-        image.setLocation(u"/foo/../bar")
-        assert image.getLocation() == "/bar"
-
-    def test_getHash(self):
-        image = self.shelf.getImage(os.path.join(PICDIR, "arlaharen.png"))
-        assert image.getHash() == "39a1266d2689f53d48b09a5e0ca0af1f"
+        imageversion = self.shelf.getImageVersionByLocation(location)
+        imageversion.locationChanged(u"/foo/../bar")
+        assert imageversion.getLocation() == "/bar"
 
-    def test_setHash(self):
-        image1 = self.shelf.getImage(os.path.join(PICDIR, "arlaharen.png"))
-        image2 = self.shelf.getImage(
-            os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
-        image2.setLocation(os.path.join(PICDIR, "arlaharen.png"))
-        h = image1.getHash()
-        self.shelf.deleteImage(h)
-        image2.setHash(h)
-        assert image2.getHash() == "39a1266d2689f53d48b09a5e0ca0af1f"
+    def test_importExifTags(self):
+        imageversion = self.shelf.getImageVersionByLocation(
+            os.path.join(PICDIR, "arlaharen.png"))
+        imageversion.importExifTags(True) # TODO: Test more.
+        imageversion.importExifTags(False) # TODO: Test more.
 
-    def test_isAlbum(self):
-        image = self.shelf.getImage(os.path.join(PICDIR, "arlaharen.png"))
-        assert not image.isAlbum()
+class TestOrphansAlbum(TestShelfFixture):
+    def test_getType(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        assert orphans.getType() == AlbumType.Orphans
 
-    def test_importExifTags(self):
-        image = self.shelf.getImage(os.path.join(PICDIR, "arlaharen.png"))
-        image.importExifTags() # TODO: Test more.
+    def test_isMutable(self):
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        assert not orphans.isMutable()
 
-class TestAllAlbumsAlbum(TestShelfFixture):
     def test_getChildren(self):
-        gamma = self.shelf.getAlbum(u"gamma")
-        assert len(list(gamma.getChildren())) == 7
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        assert len(list(orphans.getChildren())) == 2
 
     def test_getAlbumChildren(self):
-        gamma = self.shelf.getAlbum(u"gamma")
-        assert list(gamma.getAlbumChildren()) == list(gamma.getChildren())
+        orphans = self.shelf.getAlbumByTag(u"orphans")
+        epsilon = self.shelf.getAlbumByTag(u"epsilon")
+        assert list(orphans.getAlbumChildren()) == [epsilon]
 
     def test_setChildren(self):
-        gamma = self.shelf.getAlbum(u"gamma")
+        orphans = self.shelf.getAlbumByTag(u"orphans")
         try:
-            gamma.setChildren([])
+            orphans.setChildren([])
         except UnsettableChildrenError:
             pass
         else:
             assert False
 
     def test_isAlbum(self):
-        assert self.shelf.getAlbum(u"gamma").isAlbum()
+        assert self.shelf.getAlbumByTag(u"orphans").isAlbum()
 
-class TestAllImagesAlbum(TestShelfFixture):
-    def test_getChildren(self):
-        delta = self.shelf.getAlbum(u"delta")
-        assert len(list(delta.getChildren())) == 11
-
-    def test_getAlbumChildren(self):
-        delta = self.shelf.getAlbum(u"delta")
-        assert list(delta.getAlbumChildren()) == []
-
-    def test_setChildren(self):
-        delta = self.shelf.getAlbum(u"delta")
-        try:
-            delta.setChildren([])
-        except UnsettableChildrenError:
-            pass
-        else:
-            assert False
+class TestSearchAlbum(TestShelfFixture):
+    def test_getType(self):
+        zeta = self.shelf.getAlbumByTag(u"zeta")
+        assert zeta.getType() == AlbumType.Search
 
-    def test_isAlbum(self):
-        assert self.shelf.getAlbum(u"delta").isAlbum()
+    def test_isMutable(self):
+        zeta = self.shelf.getAlbumByTag(u"zeta")
+        assert not zeta.isMutable()
 
-class TestOrphansAlbum(TestShelfFixture):
     def test_getChildren(self):
-        orphans = self.shelf.getAlbum(u"orphans")
-        assert len(list(orphans.getChildren())) == 2
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        image1, image2 = list(alpha.getChildren())[0:2]
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_b = self.shelf.getCategoryByTag(u"b")
+        image1.addCategory(cat_a)
+        image2.addCategory(cat_b)
+        zeta = self.shelf.getAlbumByTag(u"zeta")
+        assert zeta
+        zeta.setAttribute(u"query", u"b")
+        children = zeta.getChildren()
+        assert list(children) == [image2]
 
     def test_getAlbumChildren(self):
-        orphans = self.shelf.getAlbum(u"orphans")
-        epsilon = self.shelf.getAlbum(u"epsilon")
-        assert list(orphans.getAlbumChildren()) == [epsilon]
+        alpha = self.shelf.getAlbumByTag(u"alpha")
+        image1, image2 = list(alpha.getChildren())[0:2]
+        cat_a = self.shelf.getCategoryByTag(u"a")
+        cat_b = self.shelf.getCategoryByTag(u"b")
+        image1.addCategory(cat_a)
+        image2.addCategory(cat_b)
+        zeta = self.shelf.getAlbumByTag(u"zeta")
+        assert zeta
+        zeta.setAttribute(u"query", u"b")
+        children = zeta.getAlbumChildren()
+        l = list(children)
+        assert list(children) == []
 
     def test_setChildren(self):
-        orphans = self.shelf.getAlbum(u"orphans")
+        zeta = self.shelf.getAlbumByTag(u"zeta")
         try:
-            orphans.setChildren([])
+            zeta.setChildren([])
         except UnsettableChildrenError:
             pass
         else:
             assert False
 
     def test_isAlbum(self):
-        assert self.shelf.getAlbum(u"orphans").isAlbum()
+        assert self.shelf.getAlbumByTag(u"zeta").isAlbum()
 
 ######################################################################
 
+removeTmpDb()
 if __name__ == "__main__":
-    removeTmpDb()
     unittest.main()