[prev in list] [next in list] [prev in thread] [next in thread]
List: gallery-checkins
Subject: [Gallery-checkins] CVS: docs/dev g20_class_api,1.13,1.14
From: Bharat Mediratta <bharat () users ! sourceforge ! net>
Date: 2001-11-29 6:52:39
[Download RAW message or body]
Update of /cvsroot/gallery/docs/dev
In directory usw-pr-cvs1:/tmp/cvs-serv27475
Modified Files:
g20_class_api
Log Message:
* Conceptually reworked the locking code. Now we use a combination
of local locks and world locks to avoid data corruption. Added a
long description of how locking works and how to use it.
* Updated the examples to use locking correctly
* Added yet more examples
Index: g20_class_api
===================================================================
RCS file: /cvsroot/gallery/docs/dev/g20_class_api,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -r1.13 -r1.14
--- g20_class_api 2001/11/28 07:26:15 1.13
+++ g20_class_api 2001/11/29 06:52:37 1.14
@@ -25,12 +25,94 @@
Things that need to be added to this API:
-----------------------------------------
+- permission changes must be tracked as exceptions in the parent
+- add examples of permission changes
+- add examples of user maintenance
- Sorting!
- Support for some kind of LinkItem (ie, a shared item?)
- Support for user groups
+- in debug mode we can register a checker to walk the tree and
+ verify that everything got saved before closing down. Theoretically
+ it won't be possible to change an item unless it's locked so this
+ should just be a sanity check.
Things that need to be documented more thoroughly in general:
-------------------------------------------------------------
+- Derived images.
+ - how they work
+ - how changes propagate
+ - short circuiting the source->source->source chain
+ - how to rebuild one
+ - how to expire one
+ - change notification
+ - real source vs. preferred source
+
+- Locking explained. There are two different types of locks:
+ 1. Local locks
+ 2. World locks
+
+ All locks are write-locks. Reading can always occur, even if an
+ object is locked or if there is a World lock. It's possible
+ that you're reading data that's changing concurrently (in another
+ thread) in which case your results may be unpredictable, but this is
+ a transitory effect that we can suppress at a later date.
+
+ After you acquire a lock (either Local or World), all items affected
+ by the lock will be automatically refreshed from the persistent
+ store. This guarantees that you're working on the latest version of
+ the data and prevents you from colliding with another change that
+ happened between the time that you got the item and the time that
+ you acquired the lock.
+
+ If you're making a change to a known set of items that will not cause
+ changes to other items outside of the set, you can get a Local
+ lockset. You can only have one Local lockset active at any given
+ time. By forcing a single lockset, we can guarantee in the core
+ code that you cannot cause a thread deadlock.
+ Rules:
+ * One process cannot have two active Local locksets.
+ * Two processes can't lock the same item at the same time.
+ * Multiple processes can each have their own lock on distinct items.
+
+ NOTE: The act of releasing a Local lock saves all objects that were
+ covered by the lock.
+
+ If you're making many changes, or a change that will affect the
+ structure of the tree then you need to acquire a World lock. If you
+ attempt such an operation without a world lock, the core code will
+ fail fast and loud. A world lock cannot exist in the presence of
+ another world lock or a Local lock. After the world lock is
+ acquired, every object in your tree will be refreshed from the
+ database to make sure that you have a current copy.
+
+ In order to acquire a world lock, you must signal that no new Local
+ locks should be acquired. So, the core code will acquire a
+ RequestForWorld lock. Once this lock has been acquired, no new
+ Local locks can be acquired. Once all Local locks have been
+ released, the WorldLock is acquired and the RequestForWorld lock is
+ released.
+
+ NOTE: The act of releasing a World lock will automatically save the
+ entire tree. to make sure that anything that needs saving is
+ flushed to the persistent store. This call will only save objects
+ that have actually changed so it should be fairly efficient.
+
+ NOTE: World locks should be acquired on as tight a piece of code as
+ possible! They only need to be acquired if you're changing
+ something that affects items in the *existing* tree. If you create
+ a bunch of new items and mess with them/interconnect them, you don't
+ need a world lock (unless the core code specifically tells you that
+ you do) until you actually attach them to the tree.
+
+ NOTE: The core code is implemented to complain if the operation it's
+ being asked to perform requires a lock, but you don't have one.
+ This will aid the application developer in finding and locking
+ everything that needs locking. The rule of thumb in the core code
+ is that if any logical branch can lead to a case where we requires a
+ World lock, then the whole function *always* requires a World lock.
+ This is more restrictive, but it will make it far easier to generate
+ robust code.
+
- How permissions work. Thumbnail sketch: you ask an item if a
specific user has PERMISSION_CAN_BLAH or PERMISSION_CANNOT_BLAH.
That item recursively asks it's ancestors for an answer. We're
@@ -43,9 +125,10 @@
- status codes. This is mostly in place. Any time you attempt an
operation that will actually commit data to the persistent store, you
- can either get a value of SUCCESS, or you can get ERROR. We may
- eventually want to return bitflags to specify what kind of error
- occurred.
+ can either get a value of SUCCESS, or you can get an int that has
+ the ERROR bit set, as well as one or more error bitflags:
+
+ ERROR_NAME_COLLISION
- Creating a new persistence backend (ie, "Filesystem", "MySQL")
This is not finalized yet. But I'm thinking that all you need to do is
@@ -60,39 +143,59 @@
ALBUMS
------
-Create the root AlbumItem. This is something that only the config
-wizard should do. It should store the id of the root in config.php
-when it's ready. The config wizard can try this every time, as only
-one album can become the root album.
-
- $rootAlbum = new AlbumItem();
- $status = $rootAlbum->becomeTheRootAlbum();
- if ($status == SUCCESS) {
- $rootAlbum->save();
- } else {
- $rootAlbum->delete();
- }
Get the root AlbumItem. Note that we're assigning by reference
- $rootAlbumId = $gallery->getRootAlbumItemId();
+ $rootAlbumId = $gallery->getItemByPath("/");
$rootAlbum =& $gallery->getItemById($rootAlbumId);
+Create the root AlbumItem. If it turns out that we have no
+root album, then it's time to create one. This will happen
+in the common init code for every request:
+
+ $rootAlbumId = $gallery->getItemByPath("/");
+ if (empty($rootAlbumId)) {
+ if ($gallery->lockWorld()) {
+ $rootAlbum = new AlbumItem();
+
+ // Arbitrary name that is never visible to the end user
+ $rootAlbum->setPath("root");
+
+ $status = $rootAlbum->becomeTheRootAlbum();
+ if ($status == SUCCESS) {
+ // Check status here, too. If this fails,
+ // alert the user.
+ } else {
+ // this is only deleting the temporary
+ // that we just created.
+ $rootAlbum->delete();
+ }
+
+ $gallery->unlockWorld();
+ }
+ }
+
Create a new album at the top level with a title and set the
isHtmlAllowed property.
+ // This does not need to be locked, as the object it's
+ // affecting is not in the tree yet.
$newAlbum = new AlbumItem();
$newAlbum->setProperty("title", "This is my new album");
$newAlbum->setProperty("isHtmlAllowed", true);
// Lock the root album
- if ($gallery->lock(array(&$rootAlbum))) {
+ if ($gallery->lockLocal(array(&$rootAlbum))) {
// Add the item and save it
$rootAlbum->addItem($newAlbum);
- $rootAlbum->save();
// Release the lock
- $gallery->unlock();
+ $status = $gallery->unlockLocal();
+ if ($status == SUCCESS) {
+ // Yay
+ } else {
+ // Check the type of error and do the right thing
+ }
} else {
// FAIL!
}
@@ -122,7 +225,40 @@
// Bad
}
+Grab the thumbnail file for this item. This may require a lock if the
+thumbnail needs to be rebuilt:
+
+ $item = // a GalleryItem
+ $thumbId = $item->getThumbnailId();
+ if (empty($thumbId)) {
+ // There's no thumbnail!
+ } else {
+ // Get the container. No need to lock yet
+ $image =& $gallery->getContainerById($thumbId);
+
+ // Is the cache expired? Note that there's a race
+ // condition here as the cache can be expired on
+ // disk but not in memory. We're ok with that for now
+ // as it's transitory and not destructive
+ if (!$image->isCacheCurrent()) {
+
+ // It's expired. Lock it down and rebuild it
+ if ($gallery->lockLocal(array(&$image))) {
+ // Rebuild the cache
+ $image->rebuildCache();
+ $status = $gallery->unlockLocal();
+ if ($status == SUCCESS) {
+ // Yay
+ } else {
+ // Boo
+ }
+ }
+ }
+ $image_file = $image->getDataFileName();
+ }
+
+
GENERAL ITEMS (photos, movies, etc)
-----------------------------------
Traverse the list of photos (or whatever else is contained
@@ -136,11 +272,32 @@
// e.g., $type is now "Album" or "Photo", etc
}
-Get the thumbnail for an item and learn its dimensions:
+Get the thumbnail for an item and learn its dimensions. This
+doesn't need locking because the dimensions are cached.
$item = // a GalleryItem
$id = $item->getThumbnailId();
$image =& $gallery->getContainerById($id);
+
+ // Is the cache expired? Note that there's a race
+ // condition here as the cache can be expired on
+ // disk but not in memory. We're ok with that for now
+ // as it's transitory and not destructive
+ if (!$image->isCacheCurrent()) {
+
+ // It's expired. Lock it down and rebuild it
+ if ($gallery->lockLocal(array(&$image))) {
+ // Rebuild the cache
+ $image->rebuildCache();
+ $status = $gallery->unlockLocal();
+ if ($status == SUCCESS) {
+ // Yay
+ } else {
+ // Boo
+ }
+ }
+ }
+
list($width, $height) = $image->getDimensions();
ITEM CREATION
@@ -161,17 +318,27 @@
// $item->getType() == "Photo"
$path = "/tmp/php712a3u";
- $name = "foo.xxx";
+ $name = "foo.xyz";
$item2 = GalleryItemFactory::createItem($path, $name);
// $item2->getType() == "Unknown"
At this point, $item is now in a temporary holding bin. Let's add it
to an existing album:
- // Acquire the appropriate lock(s)
- if ($gallery->lock(array(&$album))) {
+ // Acquire the appropriate lock(s). We're causing a structural change
+ // so we're required to get a World lock, even though it's not
+ // strictly necessary in all cases since it seems like only the
+ // target album is getting modified.
+ //
+ $album = // an AlbumItem attached to the tree
+ if ($gallery->lockWorld()) {
$album->addItem($item);
- $gallery->unlock();
+ if ($status == SUCCESS) {
+ // Yay
+ } else {
+ // Boo
+ }
+ $gallery->unlockWorld();
} else {
// FAIL!
}
@@ -180,30 +347,52 @@
800x800 bounding box:
// Acquire the appropriate lock(s)
- if ($gallery->lock(array(&$item))) {
+ if ($gallery->lockLocal(array(&$item))) {
- // First get the id of the source image container
+ // First get the id of the source image container.
$sourceId = $item->getSourceId();
- // Then create a new image container and set it's
- // source accordingly
+ // Then create a new image container and set its
+ // source accordingly.
$destImageContainer = new ImageContainer();
$destImageContainer->setDerivativeSource($sourceId);
- // Now specify a target size
+ // Now specify a target size and save the
+ // destination image.
$destImageContainer->setDerivativeCommands("scale:800");
- $destImageContainer->save();
+
+ // We have to save this guy by hand because he's not
+ // locked.
+ $status = $destImageContainer->save();
+ if ($status == SUCCESS) {
+ // Yay
+ } else {
+ // Boo
+ }
- // Finally, add the image as a resized image to the
+ // Add the image as a resized image to the
// original item.
$item->addResizeId($destImageContainer->getId());
// At this point the image hasn't been created, but it
// will be the next time it's accessed. You can trigger
- // a rebuild right now by doing:
- $destImageContainer->rebuildCache();
-
- $gallery->unlock();
+ // a rebuild without locking right now by calling
+ //
+ // $destImageContainer->rebuildCache();
+ //
+ // You don't need to lock the above because we haven't
+ // yet saved the item so nobody else can possibly know
+ // about the resized image yet. However, rebuilding is
+ // slow and we don't want to do that inside a lock (even
+ // a local lock) so do that in its own lock.
+
+ $status = $gallery->unlockLocal();
+ if ($status == SUCCESS) {
+ // Yay
+ } else {
+ // Boo
+ }
+
} else {
// FAIL!
}
@@ -211,40 +400,55 @@
Change the above resized image to be permanently sized to 200x400.
// Acquire the appropriate lock(s)
- if ($gallery->lock(array(&$imageContainer))) {
- $ids = $item->getResizeIds();
- $id = $ids[0]; // arbitrary choice here
+ $ids = $item->getResizeIds();
+ $id = $ids[0]; // arbitrary choice here
+ $imageContainer =& $gallery->getContainer($id);
- // We're unable to acquire the imageContainer's lock here
- // since we can only have one lockset at any given time.
- // however, since we have the parent item's lock we're OK
- $imageContainer =& $gallery->getItemById($id);
+ if ($gallery->lockLocal(array(&$imageContainer))) {
$imageContainer->setDerivativeCommands("resize:200,400");
- $imageContainer->save();
$imageContainer->expireCache();
- $gallery->unlock();
+ $status = $gallery->unlockLocal();
+ if ($status == SUCCESS) {
+ // Yay
+ } else {
+ // Boo
+ }
} else {
// FAIL!
}
Delete the above resized image:
- // Acquire the appropriate lock(s)
- if ($gallery->lock(array(&$imageContainer, &$item))) {
+ // First remove the resize from the item
+ if ($gallery->lock(array(&$item))) {
$ids = $item->getResizeIds();
$id = $ids[0]; // arbitrary choice here
- $imageContainer =& $gallery->getContainerById($id);
-
$item->removeResizeId($id);
- $item->save();
- $imageContainer->delete();
- $gallery->unlock();
+ $status = $gallery->unlockLocal();
+ if ($status == SUCCESS) {
+ // Yay
+ } else {
+ // Boo
+ }
} else {
// FAIL!
}
+ // Then delete the container. Technically, if the container
+ // has no derivatives and/or is not derived from any other
+ // container we wouldn't need to do any locking. But since we
+ // can't tell, we're forced to lock the world so that any
+ // notification can propagate as necessary
+ //
+ if ($gallery->lockWorld()) {
+ $imageContainer->delete();
+ $gallery->unlockWorld();
+ } else {
+ // FAIL!
+ }
+
Associate a thumbnail with an AlbumItem (ie, make a highlight) from a
PhotoItem's source:
@@ -257,23 +461,30 @@
$thumb = new ImageContainer();
$thumb->setDerivativeSource($sourceId);
$thumb->setDerivativeCommands("scale:200");
+
+ // Save this guy manually because he's not locked
$thumb->save();
- // Acquire the appropriate lock(s)
- if ($gallery->lock(array(&$albumItem))) {
+ // We have to lock the world here because the old thumbnail
+ // might have been part of a derivative chain, and if so, we'll
+ // need to make the new thumbnail assume the old thumbnail's
+ // responsibilities.
+ if ($gallery->lockWorld()) {
$oldThumbId = $albumItem->getThumbnailId();
$albumItem->setThumbnailId($thumb->getId();)
- $albumItem->save();
- $gallery->unlock();
// Delete the old thumbnail, if it exists
if (!empty($oldThumbId)) {
$oldThumb =& $gallery->getContainerById($oldThumbId);
- if ($gallery->lock(array(&$oldThumbId))) {
- $oldThumb->delete();
- $gallery->unlock();
- }
+ $oldThumb->delete();
}
+
+ $status = $gallery->unlockWorld();
+ if ($status == SUCCESS) {
+ // Yay
+ } else {
+ // Boo
+ }
} else {
// FAIL!
}
@@ -294,25 +505,29 @@
$rotated = new ImageContainer();
$rotated->setDerivativeSource($sourceId);
$rotated->setDerivativeCommands("rotate:90");
+
+ // Save this guy manually because he's not locked
$rotated->save();
- // Acquire the appropriate lock(s)
- if ($gallery->lock(array(&$photoItem))) {
+ // We have to lock the world here because the old
+ // preferred-source or the original might have been part of a
+ // derivative chain, and if so, we'll need to make the new
+ // container assume the old container's responsibilities.
+ if ($gallery->lockWorld()) {
$oldId = $photoItem->getPreferredSource();
$photoItem->setPreferredSource($rotated->getId());
- $gallery->unlock();
- }
- if (!empty($oldId)) {
- $oldData =& $gallery->getContainer($oldId);
-
- // Acquire the appropriate lock(s)
- if ($gallery->lock(array(&$oldData))) {
+ if (!empty($oldId)) {
+ $oldData =& $gallery->getContainer($oldId);
$oldData->delete();
- $gallery->unlock();
}
- }
+ $gallery->unlockWorld();
+ } else {
+ // FAIL
+ // clean up our mess
+ $rotated->delete();
+ }
COMMENTS
--------
@@ -324,9 +539,9 @@
$comment->setComment("This is a comment");
// Acquire the appropriate lock(s)
- if ($gallery->lock(array(&$albumItem))) {
+ if ($gallery->lockLocal(array(&$albumItem))) {
$item->addComment($comment);
- $gallery->unlock();
+ $gallery->unlockLocal();
} else {
// FAIL!
}
@@ -335,6 +550,7 @@
$commentIds = $item->getCommentIds();
foreach ($commentIds as $id) {
$comment = $item->getComment($id);
+ print($comment->getName());
print($comment->getComment());
}
@@ -343,27 +559,21 @@
$comment = $item->getComment($id);
// Acquire the appropriate lock(s)
- if ($gallery->lock(array(&$albumItem, &$comment))) {
+ if ($gallery->lockLocal(array(&$albumItem, &$comment))) {
$item->removeComment($id);
- $item->save();
$comment->delete();
- $gallery->unlock();
+ $gallery->unlockLocal();
} else {
// FAIL!
}
Move a comment:
// Acquire the appropriate lock(s)
- if ($gallery->lock(array(&$oldItem, &$newItem, &$comment))) {
+ if ($gallery->lockLocal(array(&$oldItem, &$newItem, &$comment))) {
$id = // id of comment to be moved
$oldItem->removeComment($id);
$newItem->addComment($id);
-
- $comment->save();
- $oldItem->save();
- $newItem->save();
-
- $gallery->unlock();
+ $gallery->unlockLocal();
} else {
// FAIL!
}
@@ -372,14 +582,50 @@
$id = // id of comment to be editted
// Acquire the appropriate lock(s)
- if ($gallery->lock(array(&$comment))) {
+ if ($gallery->lockLocal(array(&$comment))) {
$comment->setName("GI Joan");
$comment->setComment("This is a nice example");
- $comment->save();
-
- $gallery->unlock();
+ $gallery->unlockLocal();
} else {
// FAIL!
+ }
+
+GENERAL ITEM METADATA
+---------------------
+
+Set the title:
+ $item = // a GalleryItem
+ if ($gallery->lockLocal(array(&$item))) {
+ $item->setProperty("title", "This is the title");
+ $gallery->unlockLocal();
+ }
+
+Update the view count:
+ $item = // a GalleryItem
+ if ($gallery->lockLocal(array(&$item))) {
+ $count = $item->getProperty(PROPERTY_VIEWCOUNT);
+ $item->setProperty(PROPERTY_VIEWCOUNT, $count+1);
+ $gallery->unlockLocal();
+ }
+
+Rename an item:
+ $item = // a GalleryItem
+ $parent =& $item->getParent();
+
+ // It's possible that two items will simultaneously attempt to
+ // rename themselves to the same name. This is only an issue if
+ // they have the same parent so lock the item's parent also.
+ //
+ if ($gallery->lock(array(&$parent, &$item))) {
+ $status = $item->setPath("newname");
+ if ($status & ERROR) {
+ // Uh-oh
+
+ if ($status & ERROR_NAME_COLLISION) {
+ // Recoverable -- alert the user
+ }
+ }
+ $gallery->unlockLocal();
}
================================================================================
_______________________________________________
Gallery-checkins mailing list
Gallery-checkins@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/gallery-checkins
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic