Nov 292007
 

One of the things I was really impressed with while attending DLF was the presentation on the lightweight web platform being built at NCSU.  Leveraging their endeca catalog, the folks at NCSU have been able to produce a set of REST-based api for querying the catalog.  With those services, they’ve designed a mobile interface and a google widgets interface to their catalog.  It’s a good idea — one that I’m working on moving into LF.  LibraryFind already supports a SOAP-based API (which is actually my preference), but I’m running into more and more cases where a light-weight rest-based api would be nice.  Fortunately, ruby/rails makes this possible. 

Anyway, in the spirit of looking at building client services ontop of libraryfind, I’ve created a quick facebook app using libraryfind.  Whether we’ll actually use it (or be able to contribute it to the facebook app list — who knows), but it was actually pretty simple to put together.  Here’s a couple of quick screenshots (you’ll notice I’m using the iframe option — mostly because I was getting annoyed while testing the app getting dropped out of facebook.  This way, the user stays in facebook, but can query the service.

Facebook LibraryFind app Front:

image

Facebook LibraryFind Search/Results:

image

 

At this point, the app is just rendering LF in the iframe, though I imagine that an interface developed for mobile users would also work best within this environment.  I guess time (and usability testing) will tell (if there is even any interest in having something like this at all).

–TR

Technorati Tags: ,
 Posted by at 9:33 pm

Packing on the miles

 Cycling  Comments Off
Nov 202007
 

I need to thank Kyle — because of him, I’m riding quite a few more miles during the winter than normal.  In a normal year, I’d say that I put approximately 10,000 or so commuting miles on my bike (this doesn’t include my recreational riding).  However, once winter comes around, I generally start cutting back the miles a little bit by riding 1/2 my commuting distance 2 days a week, and the full distance the other days.  This cuts off about 50 miles a week.  Well, with Kyle around, I’ve been finding it much easier getting out every day in the wind and the rain and the crud.  Somehow, riding in the crud with someone seems to make it all better (at least in the morning — there’s nothing good about wet socks in the afternoon :)). 

Anyway, I’ve been tracking my miles since Sept.  Even with traveling over a week each month, taking away at least 5 riding days, I’ve logged:

  • Sept: 850 miles
  • Oct: 1050 miles
  • November (to present): 550 miles

Hopefully, all this extra riding will pay dividends come spring/summer when the recreational cycling begins and my fancy turns towards the mountains.  :)

 

–TR

 Posted by at 1:16 am
Nov 202007
 

So I’ve posted an update that fixes a few things. 

  1. Corrects the preview function (with one of the last windows service packs, it changed how some data is passed as command-line arguments.  This broke the preview option.  This should correct it).
  2. Select/Delete MARC records — fixed the Field searching (F#: mnemonic).
  3. Added the Plugin manager.  Was a little more involved than I thought it would be (see: Dynamically loading/unloading Assemblies in C#), but it looks like the loading and unloading of assemblies is working like I want it to.  No new plugins yet (still getting the first one working) — but the manager is there so I’ll let folks know when I post the first plugin — the OCLC plugin.
  4. Z39.50 update
  5. Delimited Text Translator Update (the program was hanging on certain conditions)
  6. Others.

You can download the update from: MarcEdit51_Setup.exe

 Posted by at 1:01 am
Nov 192007
 

While working on a plugin manager for a program written in C#, I found myself with a need to be able to load and unload assemblies dynamically be an application.  In C#, loading assemblies is a fairly easy prospect — one just needs to make use of the System.Reflection class.  Something like the following:

System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFile(@"c:\yourassembly.dll");

However, if you need to unload the assembly — good luck.  The .NET assembly class doesn’t include an unload method.  If you have a need to be able to dynamically load an unload assemblies, you need to work with the AppDomain class.  The .NET framework works on an Application Domain model, so for items like plugins (where you may need to load, unload or modify an assembly), you need to create an Application Domain manager to load assemblies onto.  This way, when you need to unload an assembly, you use the Unload method found within the AppDomain class. 

Of course, when dealing with plugins, you likely will need to create a new application domain for each plugin to be loaded.  This is because the you unload the appdomain, not the assemblies attached to the domains.  So for my project, I decided to create something much like the TempFileCollection.  In a global class, I decided to create a hash that stories a domain name and the domain object.  Using this method, I can do something like the following:

   1:  string path = cglobal.mglobal.AppPath() + "plugins" + System.IO.Path.DirectorySeparatorChar;
   2:              string[] files = System.IO.Directory.GetFiles(path);
   3:   
   4:              lstInstalled.Items.Clear();
   5:              foreach (string f in files)
   6:              {
   7:                  try
   8:                  {
   9:                      System.AppDomain domain = System.AppDomain.CreateDomain(System.IO.Path.GetFileName(f));
  10:                      System.IO.StreamReader reader = new System.IO.StreamReader(f, System.Text.Encoding.GetEncoding(1252), false);
  11:   
  12:                      byte[] b = new byte[reader.BaseStream.Length];
  13:                      reader.BaseStream.Read(b, 0, System.Convert.ToInt32(reader.BaseStream.Length));
  14:   
  15:                      domain.Load(b);
  16:                      System.Reflection.Assembly[] a = domain.GetAssemblies();
  17:                      int index = 0;
  18:   
  19:                      
  20:                      
  21:   
  22:                      for (int x = 0; x < a.Length; x++)
  23:                      {
  24:                          if (a[x].GetName().Name + ".dll" == System.IO.Path.GetFileName(f))
  25:                          {
  26:                              index = x;
  27:                              break;
  28:                          }
  29:                      }
  30:   
  31:                      System.Windows.Forms.ListViewItem item = new ListViewItem();
  32:   
  33:                      item.Text = a[index].GetName().Name + ".dll";
  34:                      item.SubItems.Add(a[index].GetName().Version.ToString());
  35:                      item.SubItems.Add(reader.BaseStream.Length.ToString());
  36:                      lstInstalled.Items.Add(item);
  37:                      reader.Close();
  38:                      cglobal.mglobal.domains.Add(System.IO.Path.GetFileName(f), domain);
  39:                      
  40:                  }
  41:                  catch { }
  42:              }
 

Then, if we need to unload the assembly, we can unload the domain that its attached to.  Something like:

   1:  for (int x = 0; x < lstInstalled.Items.Count; x++)
   2:              {
   3:                  if (lstInstalled.Items[x].Selected == true) {
   4:                      try {
   5:                          if (System.IO.File.Exists(cglobal.mglobal.AppPath() + "plugins" + System.IO.Path.DirectorySeparatorChar + lstInstalled.Items[x].Text)) {
   6:                              System.AppDomain.Unload((System.AppDomain)cglobal.mglobal.domains[lstInstalled.Items[x].Text]);
   7:                              cglobal.mglobal.domains.Remove(lstInstalled.Items[x].Text);
   8:                              System.IO.File.Delete(cglobal.mglobal.AppPath() + "plugins" + System.IO.Path.DirectorySeparatorChar + lstInstalled.Items[x].Text);
   9:                          }
  10:                      }
  11:                      catch {}
  12:                  }
  13:              }

Seems a little more involved that it has to be, but once you know how it works, its not that big of a deal.

–TR

 Posted by at 1:15 am
Nov 172007
 

At some point, I’ll likely move this to the LibraryFind blog.  I just realized that I couldn’t remember my login information to post to the blog — so, I’ll post here.

I’m not exactly sure if the UI changes will be made in 0.8.4 to incorporate the new spawning/pinging (I think that they will), but the next version of LibraryFind will include an extended set of API that will allow users to develop UI interfaces that allow searches to be done independent of the UI.  This was done using a spawn plugin (you can find it on rubyforge or linked in lf via svn:externals), the creation of a jobs queue, and changes in a lot of the backend server components to make this all work.  I’ve just checked into the 0.8.4-ping branch (and soon into trunk once I’ve finished testing and create some unit tests) all the code necessary to make this work.  This requires the following changes to the LF codebase:

  1. meta_search.rb refactor:  In order for this to work, I’ve had to move the cache checking code out of this file and move it into a more abstracted “connector” class.  The connector classes needed to be modified to support the new pinging functionality

  2. New models: job_query.rb, job_item.rb, cache_search_session.rb, cached_search.rb, oai_search_class.rb, z3950_search_class.rb, opensearch_search_class.rb

  3. API file changes: query_api.rb

  4. Controller changes: meta_search.rb, query_conntroller.rb, dispatch.rb, record_set.rb

  5. Migration changes: New migration has been added — 025_create_job_queues.rb

  6. Changes to the environment.rb file (these will be in the environment.rb.example file)

New SOAP API elements:

  • GetJobRecord (Queries the result set for a single job, returns the array of records)
  • GetJobsRecords(Queries the record sets for an array of jobs, returns the array of records)
  • SearchAsync (Returns an array of job ids, works like search)
  • SimpleSearchAsync (Returns an array of job ids, works like simple search)
  • CheckJobStatus (Returns a JobItem object for a single job)
  • CheckJobsStatus (Returns an array of JobItem objects for an array of jobs)

The presumed workflow with the api is as follows:

  1. Client will utilize the SearchAsync (or SimpleSearchAsync) API to initiate the search.  This will return an array of job ids.
  2. Client will periodically ping the server, using CheckJobStatus (or CheckJobsStatus) for a list of jobs and their status.  Status is an enumeration.  -1 == error, 0 == finished/idle, 1 == searching
  3. Client will retrieve records once items have been processed by calling GetJobRecord (or GetJobsRecords) for the array of records.

With this finished, I’ve completed pretty much all the current tags waiting for 0.8.4 and will be focusing on one additional enhancement that I’d like to see in 0.8.4 (if we can finish and test) or 0.8.5 — that being the addition of a REST-based XML api as well.  While I actually prefer the SOAP stack for some development, I would like to provide a REST-based api for more lightweight clients.

Anyway — that’s the news for now.

–TR

 Posted by at 12:02 am
Nov 162007
 

Jeremy I’m sure will get the tgz version of the file up and ready on the LF site soon, but the 0.8.3 instance of libraryfind has been tagged.  If you are interested in finding out more about libraryfind, see: http://www.libraryfind.org.

 

–TR

 Posted by at 11:48 pm