Izumi: Ralf - Rivet
Index: Home     | What Is Izumi | Misc Links   | Random Thoughts | Too Much To Read | The Rant Vault | Quotes
Dev:   Projects | Ideas For Dev | Nerdkill | Rig | Hint

Rivet is a project description page for Rivet.
Site License And Disclaimer as well as contact information are available here.

$Id: Rivet.izu,v 1.2 2005/12/19 05:46:49 ralf Exp $

1. Introduction

1.1. Goal

20050426

Rivet stands for "Ralf Image Viewer, Exif and Transfer" (*).

Summary: A desktop application to manipulate a library of images, download them for a camera, and synchronize repositories with RIG.

The detailed goal of Rivet is to:

Implementation:

1.2. Description

1.3. Milestones

20050426

Milestones:

1.3.0 M0: View and Download

20050426

1.3.1 M1: Exif and renaming

20050426

1.3.2 M2: RIG upload

20050426

How exactly this is going to happen remains to be seen:

Several methods are probably needed, to adapt to different usage patterns.


2. Plan

2.1. M0: Initial prototype

To be done (by decreasing priority):

20050722 [1.N] Classes: RImagesView, add & manage scrollbar
20050722 [1.N] Classes: RImagesView, display image name

20050717 [1.N] Classes: RImage, load/store all EXIF info
20050717 [1.N] Classes: RImagesView, store ImgInfo in hashtable specific to current album
20050717 [1.N] Classes: RImagesView, expire hashtable ImgInfo for albums too old 

20050521 [1.N] Project: Add WIA reference
20050521 [1.N] Classes: RWiaImport, skeleton, basic import in currently selected directory
20050521 [1.N] Classes: Refresh image view after import

20050521 [1.N] Classes: RDirViewControl, ability to select an image
20050521 [1.N] Classes: RMainWindow, add info panel
20050521 [1.N] Classes: RMainWindow, display info from current selection

20050521 [1.N] Classes: RMainWindow, add rename panel (using regexp and replace)
20050521 [1.N] Classes: RRenameTool, skeleton
20050521 [1.N] Classes: RRenameTool, rename existing items from current dir using panel info
20050521 [1.N] Classes: Refresh image view after import
20050521 [2.N] Classes: RImage, implement cache with invalidation

Finished (by decreasing date):

20050722 [1.F] Classes: RImagesView, abort asynchronous operation when album changed or resized

20050721 [1.N] Classes: RImagesView, update singular image items when load completed
20050721 [1.F] Classes: RImagesView, start asynchronous load of image items

20050717 [1.F] Classes: RImagesView, display empty image placeholders
20050717 [1.F] Classes: RImage, load/store thumbnail, size, name (metadata)

20050712 [1.F] Classes: RImagesView, display image list
20050712 [1.F] Classes: RImage, implemented crude creation of thumbnail & get size
20050712 [1.F] Classes: RMainForm+Ex, Using RImagesView instead of WinForms\' ListView
20050626 [1.F] Classes: Created RImagesView (custom control, ex-RDirViewControl)
20050530 [1.F] Classes: Implemented IDisposable for RImage, RDir, RFSItem, RAppState
20050530 [1.F] Classes: RMainFormEx, display thumbnail in list view in large icon mode
20050530 [1.F] Classes: RImage, return Thumbnail for image (computed once only)
20050530 [1.F] Classes: RMainForm+Ex, load directory tree for current library
20050530 [1.F] Classes: RDir, load directories and images with filter
20050530 [1.F] Classes: RFSItem, properties Name, Parent, FullPath
20050530 [1.F] Classes: Added RFSItem, RDir, RImage skeletons with NUnit test
20050529 [1.F] Classes: Added missing LibVersion in RivetLib and RivetUI
20050529 [1.F] Classes: RMain added in RivetApp to load RivetUI.RMainModule
20050529 [1.F] Classes: Reorganized, adding RivetUI lib with all forms and RMainModule
20050528 [1.F] Classes: RMainForm+Ex, Load last current library at startup
20050524 [1.F] Classes: RAppState, create skeleton with Libraries and CurrentLibrary
20050524 [1.F] Classes: RLibrary, created skeleton with NUnit and AbsRootPath
20050524 [1.F] Classes: RMainFormEx, added location combo box management
20050522 [1.F] Classes: RMainFormEx, implement logic part of RMainForm
20050522 [1.F] Classes: Load/save window pos for RDebugForm/RPrefForm
20050522 [1.F] Classes: RPrefForm, empty skeleton extracted from Xeres
20050522 [1.F] Classes: RMainForm, create basic UI (tree control, base folder)
20050522 [1.F] Project: Use AppSkeleton

2.2. Milestone 1

2.3. Milestone 2


3. Notes

(Notes are given in chronological order.)


«»  2005/04/26 «» Architecture  «»

Basic architecture, from a class point of view:

Note about UI:

Note about cache:


«»  2005/05/12 «» Name  «»

Rivet stands for "Ralf Image Viewer, Exif and Transfer".
Yes, I have to admit I came up with the name first and tried to retrofit an acronym to it. Got a problem with that? :-)


«»  2005/05/21 «» Absolute Minimum  «»

The absolute minimum for a prototype to do anything useful:

Expansion:


«»  2005/05/22 «» Dual class for WinForm  «»

RMainFormEx override RMainForm and contains all the logic of the UI.

UI elements are declared (via the VS.Net resource editor) in RMainForm. As much of possible all actions performed by the UI are implemented in RMainFormEx rather than directly in RMainForm.

The goal is to separate the pure UI widget management from the logic of the window, in the hope it may be easier later to use a different UI kit (GTK#, etc.)

In a first test, RMainFormEx directly derives from RMainForm and thus will access WinForm widgets directly.

Note that the distinction used here is more or less similar to what VS.Net 2005 forces one to do with partial classes for WinForms and C# 2.0.


«»  2005/05/29 «» Tri class separation for WinForm  «»

I setup my NUnit testing by having a separate Console Application that contains a main that invokes the NUnit testing framework on a set of listed libraries.

This separate project also contains the NUnit classes, so I have a library Foo containing a class Bar, there will be a class TestBar in the TestConsole project. Obviously TestBar will need to import Bar, thus the library Foo must be referenced in the TestConsole project. So far so good.

Unfortunately, I cannot reference an EXE this way. This means that I cannot directly have a NUnit class which test functionnality located in the main application.

OTOH when I created the skeleton I kind of arbitrarily though it would be good for every application (i.e. project producing an EXE) to have a sidekick class library (producing a DLL) and has much as possible move all the logic in the DLL and keep only the minimum UI in the main EXE. The idea was that once the application gets finalized, fixes will probably be either in the logic part or the UI part and maybe only one of those would need updating. Of course there's still a pretty tight coupling between those two.

So I just started doing just that, by experimenting with the following layout:

With this layout, I can have both RMainFormEx and RAppState in the library, only RMainForm needs to be in the application project.

Actually that's not even true as there is another class, RMainModule, which is the only one that really needs to be in the application project. It could reference RMainForm from the library. So later I may want to do that -- move everything in the library and only keep a minimal loader/boostrap/main entry point in the application's EXE. Right now this is mostly irrelevant.

Anyway I started with RMainFormEx placed in the main application project, next to RMainForm. Now I want to adapt the layout as described above. There's one more problem: I can't have RMainForm in the application and RMainFormEx in the library with RMainFormEx inheriting from RMainForm. The reason is simply because the library cannot reference the main application (it's an EXE, not a DLL, and anyway if both depend on each other that makes circular references... not a good thing.)

Basically I cannot have this:

 [  .Net  ]        [ Application ]     [   Library          Project      ]
                   [   Project   ]     [                                 ]
Windows.Form
     |
     +--------<|---------+
                         |
                   +-------------+                         +-------------+
                   |  RMainForm  |                         |  RAppState  |
                   +-------------+                         +-------------+
                         |                                        |
                         +---------<|---------+                   |
                                              |                   |
                                       +-------------+            |
                                       | RMainFormEx |- -(uses)- -+
                                       +-------------+
                  +--------------+            |
                  |  RMainModule |- -(uses)- -+
                  +--------------+

The solution is to introduce an abstract interface that defines the callbacks uses in RMainForm and implemented in RMainFormEx, like this:

 [  .Net  ]        [ Application ]     [   Library          Project      ]
                   [   Project   ]     [                                 ]
                                       +-------------+
Windows.Form                           | RIMainForm  |
     |                                 +-------------+
     |                                        |
     |                                        |
     +--------<|---------+---------|>---------+
                         |
                   +-------------+                         +-------------+
                   |  RMainForm  |                         |  RAppState  |
                   +-------------+                         +-------------+
                         |                                        |
                         +---------<|---------+                   |
                                              |                   |
                                       +-------------+            |
                                       | RMainFormEx |- -(uses)- -+
                                       +-------------+
                                              |       
                                              |       
                  +--------------+            |       
                  |  RMainModule |- -(uses)- -+       
                  +--------------+                    

Of course, it becomes just much easier to move RMainForm in the library and not use an extract abstract interface which is merely a workaround:

 [  .Net  ]        [ Application ]     [   Library          Project      ]
                   [   Project   ]     [                                 ]
Windows.Form
     |
     +-----------------<|---------------------+      
                                              |      
                                       +-------------+     +-------------+
                                       |  RMainForm  |     |  RAppState  |
                                       +-------------+     +-------------+
                                              |                   |
                                              ^                   |
                                              |                   |
                                       +-------------+            |
                                       | RMainFormEx |- -(uses)- -+
                                       +-------------+
                  +--------------+            |
                  |  RMainModule |- -(uses)- -+
                  +--------------+

This is still not completly satisfactory.

The thing is that the abstract interface does not add anything substancial except the need to declare callbacks in three places instead of two. The interface is a workaround to a project's reference issue, not a design issue. And anything that reduces the amount of code needed reduces the potential for errors, so it's a good thing.

RMainForm and RMainFormEx will always have a tight coupling, that is I would have used partial classes if I had been coding this in C# 2.0.

On the other hand it forces me to put all the UI stuff in the library whereas I said before I wanted to have only the "logic" part in there. So I want it in a library but not in the library. Hmm?

The obvious solution is to use two libraries:

Now the RMainModule is accessed by the forms to get the global form instances via a centralized class rather than by cross-referencing them (i.e. to get the main form from the pref window, ask the main module about the form instead of having a global variable with the form.) This way I can avoid global variables completly by having static methods in the main module return the main form.

If I were to move all forms in the UI library, they couldn't access the main module class (since the library can't reference the application.) So I need to move the RMainModule class in the UI library too.

All what's left in the application project is a minimal class with a static main that instantiates the RMainModule class. This one in turn will create the main form. RMainModule should be a singleton class.

So at the end we get this layout:

 [  .Net  ]        [ Application ]     [ UI  Library ]     [   Library   ]
                   [   Project   ]     [   Project   ]     [   Project   ]
Windows.Form
     |
     +-----------------<|---------------------+
                                              |
                                       +-------------+     +-------------+
                                       |  RMainForm  |     |  RAppState  |
                                       +-------------+     +-------------+
                                              |                   |
                                              ^                   |
                                              |                   |
                                       +-------------+            |
                                       | RMainFormEx |- -(uses)- -+
                                       +-------------+
                                              |
                                            (uses)
                                              |
                                       +-------------+
                                       | RMainModule |
                                       +-------------+
                  +--------------+            |
                  |     RMain    |- -(uses)- -+
                  +--------------+

Now the only thing is that it is important to avoid cross or circular references between libraries. References between the libraries should always be able to be represented by a tree with no backwards links.

This is easily enforces since VS.Net will not let you reference a library that may cause a circular reference.

In this case we have the following dependency graph for the various libraires and applications:

LibUtils
   |
   +--------------------------+       
   |                          |
   |                          v
   |                     LibUtilsTests
   v                          |
RivetLib                      |
   |                          |
   +------------------+       |
   |                  |       |
   |            RivetLibTests |
   v                  |       |
RivetUI               |       |
   |                  |       |
   +-----------+      |       |
   |           |      |       |
   |     RivetUITests |       |
   |           |      |       |
   |           +------+-------+
   |                  |
   v                  v
RivetApp         TestsConsole


«»  2005/05/30 «» IDisposable  «»

I implemented a bunch of stuff almost as planned today: RDir, RImage, both deriving from RFSItem (instead of RFileItem), loading directories and files (with a regexp filter), current selection in library and directory and displaying the images as a large icon list view.

That last one was a quick prototype hack. I will need to replace the ListView by a custom view. The ListView display only 8-bpp images by default .Net 1.1 and in the end I will want something more fancy than what a list view can do. But for a first prototype, it's more than OK.

The simple design I planned with RFileItem (now called RFSItem), RDir and RImage worked just fine. Yet I had forgotten something which is platform specific: if the image is going to contain a thumbnail stored as a bitmap, it needs a way to dispose of the bitmap when no longer needed.
This is simply achieved by implementing
IDisposable and freeing the bitmap resource in Dispose() yet it up to the client code to take care of that.

The alternative offered by .Net is to dispose of the resources in an overridden Finalize() methods. This is discourages as there are many limitations and issues associated with finalization (order is not guaranteed, cross-object references are not guaranteed, the thread is unknown, etc.) Disposing of a GDI+ resource in Finalize() is definitely a bad idea. As the documentation for Finalize() says, if one needs control another scheme should be used and IDisposable offers exactly that. Control here means that it is up to the application's client code to take care of disposing things in the right order, which is a lot easier than it may seem:

Also it is safe for each dispose method to set the manipulated object reference to null (f.ex. the image will set the thumbnail reference to null). This means it is safe to dispose of the same object several times (probably by mistake or bad design.) This way, if the Dispose() method is called but then the resource is accessed again, it will be correctly reloaded. This works because accessing a resource via a property get will load the corresponding resource, of course. This scheme could be implemented on purpose, for example to free resources that have not been used for a while (typically when several libraries of images have been loaded by the user, it may make sense to dispose of the other loaded libraries after a timeout.)

So overall that's a nice addition to the design even though I completely missed at first.

It's rather implementation specific too. Actually is it? I don't remember seeing a IDisposable interface used so prominently in Java, for example. In this case, the need for it arose from the fact that GDI+ resources need to be disposed of properly and this may be true of other interop resources. But in fact the same scheme can be used as a way to free memory temporarily. The resource could be a file descriptor object that one may want to close after a certain delay of inactivity. Or in this application I'd like to unload the data of all but the current library.

It makes a good pattern too: objects that are loaded on demand and unloaded when not necessary. Although the design is radically different, this is very close to the example given as a motivation behind Proxy(207) in Design Patterns yet it is actually more an example of the Lazy Instantiation Pattern with the added motivation of release the "hungry" resource at any time.

Now for the tricky unresolved (yet) question: how do I write a NUnit test for a class that automatically loads a resource when accessing a property and dispose it later, using solely its public interface? Assuming I have a test that check the resource is loaded correctly, if I dispose it by calling Dispose() I can't then use the property to check it has been disposed since that would load the resource again...
Obviously the solution is to trick the class into loading a resource which the unit test can check. For example that resource could be an object which instance is know to the test class and the test class can check some counter changed when the instance is created or disposed.
The problem I have with that is that it forces the tested class to be redesigned in order to make it testable. Thus some complexity is added just for the test whereas that complexity would not need to be there if there was no testing done... Hmm?


«»  2005/07/17 «» Image Thumbnails, the wrong way and the good way  «»

So a few days ago I create the custom control to display the thumbnails for the current selected directory.

I hacked a way to get the thumbnails and ended up with a bunch of useless code and an horrible implementation. I'm not sure what I was thinking but it was definitely the wrong way to do it:

As you can image the result is incredibly unusable. Loading a directory with a dozen images starts eating memory like hell. That's because all images in the directory get loaded in memory and never released -- why, I never bothered writing any code that would unload them after a while! And anyway, I don't need to have more than one image loaded in memory.

Plus the whole thing is completly blocking.

So let's think about the right way to do it.

First, on the RImage side:

Generally speaking I want to avoid spending time to get all infos I might need unless I really need them. On the other hand, I don't want to load an image twice just because two set of informations are not collected in the same loop.
There are two possible approaches:

On the image view side, I need asynchronous processing. The image view is first cleared, then image items are loaded and the view is refreshed to start drawing.

From a user's perspective, I want to see items appear as they get loaded. At first the view already knows its target thumbnail size and the leaf name of each image so it can display an empty rectangle for each image and their name. Once this is done, it can start fetching the thumbnail and the size for each image. Now I know that I don't really need the size before anything else, so coming back to the RImage, I can simply load the thumbnail and everything at once, including the EXIF tags.

Now coming back to the album, when changing albums and coming back to the same album, it would be nice if the image view didn't have to reload all the information.

The simpliest way to achieve that would be to keep the hashtable of ImgInfo around for the previous albums. Then either on a timer or on a size threshold (number of items or memory), older albums should be removed to avoid keeping too much stuff around in memory.


«»  2005/12/17  «»

Resources:


Site License

Creative Commons License
This work is licensed by Raphaël Moll under a Creative Commons License.

Options
Color Theme: Gray  | Blue  | Black | Sand  | Khaki  | Egg  | None

Web ralf.alfray.com Powered by Google

Display Izumi & PHP Credits

Stats
459 accesses, 1 access from 38.107.179.208
Visited 4 times by Google, last 2011/10/24 09:16
Visited 13 times by Yahoo!, last 2011/10/10 14:55
Visited 1 times by MSN, last 2011/06/17 06:41

< Generated in 0.65 seconds the 02/06/2012, 09:28 AM by Izumi 1.1.4 >