OSDN Git Service

Add GUI support for install, upgrade, and remove action scheduling.
authorKeith Marshall <keithmarshall@users.sourceforge.net>
Fri, 16 Nov 2012 16:52:48 +0000 (16:52 +0000)
committerKeith Marshall <keithmarshall@users.sourceforge.net>
Fri, 16 Nov 2012 16:52:48 +0000 (16:52 +0000)
ChangeLog
src/guimain.h
src/guixmld.cpp
src/pkgbase.h
src/pkgdata.cpp
src/pkgdeps.cpp

index a786cb0..48b3965 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,45 @@
+2012-11-16  Keith Marshall  <keithmarshall@users.sourceforge.net>
+
+       Add GUI support for install, upgrade, and remove action scheduling.
+
+       * src/guimain.h (EXTERN_C): Ensure it is defined.
+       (pkgActionItem): Add forward declaration of class name.
+       (pkgMarkSchedule): New EXTERN_C function; declare and prototype it.
+       (AppWindowMaker::OnClose): New virtual method; declare it.
+       (AppWindowMaker::Schedule): New public method; declare it.
+       (AppWindowMaker::UnmarkSelectedPackage): Likewise.
+
+       * src/pkgbase.h (pkgActionItem::HasAttribute) [this == NULL]: Add
+       protective logic; ignore attempts to access non-existent objects.
+       (pkgActionItem::SelectPackage) [this == NULL]: Likewise.
+       (pkgActionItem::Selection) [this == NULL]: Likewise.
+       (pkgActionItem::GetReference): New overloaded public method; declare
+       prototype variant with argument type of pkgXmlNode*.
+       (pkgActionItem::Unapplied): New public method; declare it.
+       (pkgActionItem::CancelScheduledAction): Likewise.
+       (pkgXmlDocument::Schedule): Change return type from void to
+       pkgActionItem*; add default argument values.
+
+       * src/guixmld.cpp (AppWindowMaker::OnCommand): Add menu case hooks...
+       [IDM_PACKAGE_INSTALL]: ...to schedule package 'install' action...
+       [IDM_PACKAGE_UPGRADE]: ...package 'upgrade' action...
+       [IDM_PACKAGE_REMOVE]: ...package 'remove' action, or...
+       [IDM_PACKAGE_UNMARK]: ...to cancel any of these scheduling requests.
+       [IDM_REPO_QUIT]: Correct typo; s/WM_QUIT/WM_CLOSE/ in comment.
+
+       * src/pkgdata.cpp (pkgMarkSchedule): Implement it.
+       (pkgActionItem::CancelScheduledAction): Implement it.
+       (pkgActionItem::Unapplied): Implement it.
+       (AppWindowMaker::UpdatePackageMenuBindings): Improve comments.
+       (AppWindowMaker::UnmarkSelectedPackage): Implement it.
+       (AppWindowMaker::Schedule): Implement it.
+       (AppWindowMaker::OnClose): Implement it.
+
+       * src/pkgdeps.cpp (pkgActionItem::GetReference): Implement overload.
+       (pkgXmlDocument::Schedule): Change return type, and return value.
+       [this == NULL]: Add protective logic; ignore non-existent objects.
+       [name == NULL]: Do not schedule; just return existing action list.
+
 2012-11-02  Keith Marshall  <keithmarshall@users.sourceforge.net>
 
        Add dynamically enabled state control for package menu items.
index 8f7350f..0b5cce0 100644 (file)
@@ -108,6 +108,17 @@ class pkgXmlNode;
 class pkgXmlDocument;
 class pkgProgressMeter;
 class DataSheetMaker;
+class pkgActionItem;
+
+#ifndef EXTERN_C
+# ifdef __cplusplus
+#  define EXTERN_C  extern "C"
+# else
+#  define EXTERN_C
+# endif
+#endif
+
+EXTERN_C void pkgMarkSchedule( HWND, pkgActionItem * );
 
 class AppWindowMaker;
 inline AppWindowMaker *GetAppWindow( HWND lookup )
@@ -144,6 +155,7 @@ class AppWindowMaker: public WTK::MainWindowMaker
     virtual long OnCommand( WPARAM );
     virtual long OnNotify( WPARAM, LPARAM );
     virtual long OnSize( WPARAM, int, int );
+    virtual long OnClose();
 
     int LayoutEngine( HWND, LPARAM );
     static int CALLBACK LayoutController( HWND, LPARAM );
@@ -155,13 +167,14 @@ class AppWindowMaker: public WTK::MainWindowMaker
 
     HWND PackageListView;
     void InitPackageListView( void );
+    void UpdatePackageMenuBindings( void );
+    void Schedule( unsigned long, const char * = NULL, const char * = NULL );
+    void UnmarkSelectedPackage( void );
 
     DataSheetMaker *DataSheet;
     WTK::ChildWindowMaker *TabDataPane;
     HWND PackageTabControl, PackageTabPane;
     void InitPackageTabControl();
-
-    void UpdatePackageMenuBindings( void );
 };
 
 #endif /* ! RC_INVOKED */
index cf9112c..3cd6e64 100644 (file)
@@ -27,6 +27,7 @@
 #include "guimain.h"
 #include "pkgbase.h"
 #include "pkgkeys.h"
+#include "pkgtask.h"
 
 #include <unistd.h>
 #include <wtkexcept.h>
@@ -409,6 +410,39 @@ long AppWindowMaker::OnCommand( WPARAM cmd )
       WTK::GenericDialogue( AppInstance, AppWindow, IDD_HELP_ABOUT );
       break;
 
+    case IDM_PACKAGE_INSTALL:
+      /*
+       * Initiated by selecting the "Mark for Installation" option
+       * from the "Package" menu, this request will schedule the
+       * currently selected package, and any currently unfulfilled
+       * dependencies, for installation.
+       */
+      Schedule( ACTION_INSTALL );
+      break;
+
+    case IDM_PACKAGE_UPGRADE:
+      /*
+       * Initiated by selecting the "Mark for Upgrade" option
+       * from the "Package" menu, this request will schedule the
+       * currently selected package, and any currently unfulfilled
+       * dependencies, for upgrade or installation, as appropriate.
+       */
+      Schedule( ACTION_UPGRADE );
+      break;
+
+    case IDM_PACKAGE_REMOVE:
+      /*
+       * Initiated by selecting the "Mark for Removal" option
+       * from the "Package" menu, this request will schedule the
+       * currently selected package for removal.
+       */
+      Schedule( ACTION_REMOVE );
+      break;
+
+    case IDM_PACKAGE_UNMARK:
+      UnmarkSelectedPackage();
+      break;
+
     case IDM_REPO_UPDATE:
       /*
        * This request is initiated by selecting "Update Catalogue"
@@ -425,7 +459,7 @@ long AppWindowMaker::OnCommand( WPARAM cmd )
     case IDM_REPO_QUIT:
       /*
        * This request is initiated by selecting the "Quit" option
-       * from the "Repository" menu; we respond by sending a WM_QUIT
+       * from the "Repository" menu; we respond by sending a WM_CLOSE
        * message, to terminate the current application instance.
        */
       SendMessage( AppWindow, WM_CLOSE, 0, 0L );
index 4cd1956..f85e716 100644 (file)
@@ -294,12 +294,18 @@ class pkgActionItem
     unsigned long SetAuthorities( pkgActionItem* );
     inline unsigned long HasAttribute( unsigned long required )
     {
-      return flags & required;
+      return (this != NULL) ? flags & required : 0UL;
     }
+    pkgActionItem* GetReference( pkgXmlNode* );
     pkgActionItem* GetReference( pkgActionItem& );
     pkgActionItem* Schedule( unsigned long, pkgActionItem& );
+    inline unsigned long CancelScheduledAction( void );
     inline void SetPrimary( pkgActionItem* );
 
+    /* Method to check for residual unapplied changes.
+     */
+    inline unsigned long Unapplied( void );
+
     /* Methods for defining the selection criteria for
      * packages to be processed.
      */
@@ -310,13 +316,13 @@ class pkgActionItem
     {
       /* Mark a package as the selection for a specified action.
        */
-      selection[ opt ] = pkg;
+      if (this != NULL) selection[ opt ] = pkg;
     }
     inline pkgXmlNode* Selection( int mode = to_install )
     {
       /* Retrieve the package selection for a specified action.
        */
-      return selection[ mode ];
+      return (this != NULL) ? selection[ mode ] : NULL;
     }
     void ConfirmInstallationStatus();
 
@@ -452,7 +458,7 @@ class pkgXmlDocument : public TiXmlDocument
 
     /* Methods for compiling a schedule of actions.
      */
-    void Schedule( unsigned long, const char* );
+    pkgActionItem* Schedule( unsigned long = 0UL, const char* = NULL );
     pkgActionItem* Schedule( unsigned long, pkgActionItem&, pkgActionItem* = NULL );
     void RescheduleInstalledPackages( unsigned long );
 
index 036a193..f158735 100644 (file)
@@ -34,6 +34,7 @@
 #include "pkgbase.h"
 #include "pkgdata.h"
 #include "pkgkeys.h"
+#include "pkginfo.h"
 #include "pkglist.h"
 #include "pkgtask.h"
 
@@ -940,7 +941,9 @@ void AppWindowMaker::UpdatePackageMenuBindings()
      */
     for( int item = IDM_PACKAGE_UNMARK; item <= IDM_PACKAGE_REMOVE; item++ )
     {
-      /* ...evaluating an independent state flag for each...
+      /* ...evaluating an independent state flag for each,
+       * setting it as non-zero for menu items which may be
+       * made "selectable", or zero otherwise...
        */
       int state_flag = state;
       switch( item )
@@ -950,21 +953,41 @@ void AppWindowMaker::UpdatePackageMenuBindings()
         * the currently selected list view item...
         */
        case IDM_PACKAGE_INSTALL:
+         /*
+          * "Mark for Installation" is available for packages
+          * which exist in the repository, (long-term or new),
+          * but which are not yet identified as "installed".
+          */
          state_flag &= PKGSTATE_FLAG( AVAILABLE )
            | PKGSTATE_FLAG( AVAILABLE_NEW );
          break;
 
        case IDM_PACKAGE_REMOVE:
        case IDM_PACKAGE_REINSTALL:
+         /*
+          * "Mark for Removal" and "Mark for Reinstallation"
+          * are viable selections only for packages identified
+          * as "installed", (current or upgradeable).
+          */
          state_flag &= PKGSTATE_FLAG( INSTALLED_CURRENT )
            | PKGSTATE_FLAG( INSTALLED_OLD );
          break;
 
        case IDM_PACKAGE_UPGRADE:
+         /*
+          * "Mark for Upgrade" is viable only for packages
+          * identified as "installed", and then only when an
+          * upgrade has been published.
+          */
          state_flag &= PKGSTATE_FLAG( INSTALLED_OLD );
          break;
 
        case IDM_PACKAGE_UNMARK:
+         /*
+          * The "Unmark" facility is available only for packages
+          * which have been marked, (perhaps inadvertently), for
+          * any of the preceding actions.
+          */
          state_flag &= PKGSTATE_FLAG( AVAILABLE_INSTALL )
            | PKGSTATE_FLAG( UPGRADE ) | PKGSTATE_FLAG( DOWNGRADE )
            | PKGSTATE_FLAG( REMOVE ) | PKGSTATE_FLAG( PURGE )
@@ -972,6 +995,9 @@ void AppWindowMaker::UpdatePackageMenuBindings()
          break;
 
        default:
+         /* When none of the preceding is applicable, the menu
+          * item should not be selectable.
+          */
          state_flag = 0;
       }
       /* ...and set the menu item enabled state accordingly.
@@ -981,6 +1007,232 @@ void AppWindowMaker::UpdatePackageMenuBindings()
   }
 }
 
+EXTERN_C void pkgMarkSchedule( HWND pkglist, pkgActionItem *actions )
+{
+  /* Helper routine to update the status icons within the package
+   * list view, to reflect scheduled actions in respect of the package
+   * associated with each.
+   */
+  LVITEM lookup;
+  lookup.iItem = 0;
+  lookup.iSubItem = 0;
+  lookup.mask = LVIF_IMAGE | LVIF_PARAM;
+  while( ListView_GetItem( pkglist, &lookup ) )
+  {
+    /* Traverse the displayed list of packages from top to bottom...
+     */
+    unsigned long op;
+    pkgActionItem *ref = actions->GetReference( (pkgXmlNode *)(lookup.lParam));
+    if( ref != NULL )
+    {
+      if( (op = ref->HasAttribute( ACTION_MASK )) != 0UL )
+      {
+       /* ...identifying any action scheduled on each package,
+        * and selecting the appropriate list view icon...
+        */
+       switch( op )
+       {
+         case ACTION_INSTALL:
+           /*
+            * ...for packages scheduled for installation...
+            *
+            * FIXME: we should also consider that such packages
+            * may have been scheduled for reinstallation.
+            */
+           lookup.iImage = PKGSTATE( AVAILABLE_INSTALL );
+           break;
+
+         case ACTION_UPGRADE:
+           /*
+            * ...for packages scheduled for upgrade...
+            */
+           lookup.iImage = PKGSTATE( UPGRADE );
+           break;
+
+         case ACTION_REMOVE:
+           /*
+            * ...for packages scheduled for removal.
+            */
+           lookup.iImage = PKGSTATE( REMOVE );
+       }
+      }
+      else
+      { /* A previously scheduled action has been cancelled.
+        */
+       pkgActionItem avail;
+       pkgXmlNode *rel = ref->Selection();
+       if( rel == NULL ) rel = ref->Selection( to_remove );
+       rel = rel->GetParent()->FindFirstAssociate( release_key );
+       while( rel != NULL )
+       {
+         /* Examine each available release specification for the nominated
+          * package; select the one which represents the latest (most recent)
+          * available release.
+          */
+         avail.SelectIfMostRecentFit( rel );
+
+         /* Also check for the presence of an installation record for each
+          * release; if found, mark it as the currently installed release;
+          * (we assign the "to-remove" attribute, but we don't action it).
+          */
+         if( rel->GetInstallationRecord( rel->GetPropVal( tarname_key, NULL )) != NULL )
+           avail.SelectPackage( rel, to_remove );
+
+         /* Cycle, until all known releases have been examined.
+          */
+         rel = rel->FindNextAssociate( release_key );
+       }
+       avail.ConfirmInstallationStatus();
+       if( (rel = avail.Selection( to_remove )) == NULL )
+         lookup.iImage = PKGSTATE( AVAILABLE );
+       else
+       {
+         pkgSpecs current( rel );
+         pkgSpecs latest( avail.Selection() );
+         lookup.iImage = (latest > current) ? PKGSTATE( INSTALLED_OLD )
+           : PKGSTATE( INSTALLED_CURRENT );
+       }
+      }
+      /* Apply new icon selection...
+       */
+      ListView_SetItem( pkglist, &lookup );
+    }
+    /* ...and move on to the next list view entry.
+     */
+    lookup.iItem++;
+  }
+}
+
+void AppWindowMaker::Schedule
+( unsigned long action, const char *bounds, const char *pkgname )
+{
+  /* GUI menu driven interface to the pkgActionItem task scheduler;
+   * it constructs a pseudo-argument string, emulating the effect of
+   * parsing a CLI argument, then passes this to the CLI scheduler
+   * API class method.
+   */
+  if( pkgname == NULL )
+  {
+    /* Initial entry on menu item selection; package name has not
+     * yet been identified, so find the selected list view item...
+     */
+    LVITEM lookup;
+    lookup.iItem = (PackageListView != NULL)
+      ? ListView_GetNextItem( PackageListView, (WPARAM)(-1), LVIS_SELECTED )
+      : -1;
+
+    /* ...and look up the package name identified within it.
+     */
+    const char *pkg, *fmt = "%s-%s";
+    pkgXmlNode *ref = pkgListSelection( PackageListView, &lookup );
+    if( (pkg = ref->GetContainerAttribute( name_key, NULL )) != NULL )
+    {
+      /* We now have a valid package name; check for a
+       * component package association.
+       */
+      const char *cpt;
+      if( (cpt = ref->GetPropVal( class_key, NULL )) == NULL )
+      {
+       /* Current list view selection represents a
+        * non-component package; encode its name only
+        * as a string argument, using only the final
+        * string field of the format specification.
+        */
+       char pkgspec[ 1 + snprintf( NULL, 0, fmt + 3, pkg ) ];
+       snprintf( pkgspec, sizeof( pkgspec ), fmt + 3, pkg );
+
+       /* Recurse, to capture any supplied version bounds
+        * specification, and ultimately schedule the action.
+        */
+       Schedule( action, bounds, pkgspec );
+      }
+      else
+      { /* Current list view selection represents a
+        * package name qualified by a component name;
+        * use the full format specification to encode
+        * the fully qualified package name.
+        */
+       char pkgspec[ 1 + snprintf( NULL, 0, fmt, pkg, cpt ) ];
+       snprintf( pkgspec, sizeof( pkgspec ), fmt, pkg, cpt );
+
+       /* Again, recurse to capture any supplied version
+        * bounds specification, before ultimately scheduling
+        * the selected action.
+        */
+       Schedule( action, bounds, pkgspec );
+      }
+    }
+  }
+  else if( bounds != NULL )
+  {
+    /* Recursive entry, after package name identification,
+     * but with supplied version bounds specification yet
+     * to be resolved; append the bounds specification to
+     * the package name, as it would be in a CLI argument...
+     */
+    const char *fmt = "%s=%s";
+    char pkgspec[ 1 + snprintf( NULL, 0, fmt, pkgname, bounds ) ];
+    snprintf( pkgspec, sizeof( pkgspec ), fmt, pkgname, bounds );
+    /*
+     * ...then recurse a final time, to schedule the action.
+     */
+    Schedule( action, NULL, pkgspec );
+  }
+  else
+  { /* Final recursive entry, with pkgname argument in the
+     * same form as a CLI package name/bounds specification
+     * argument; hand it off to the CLI scheduler, capturing
+     * the resultant schedule of actions, and update the list
+     * view state icons to reflect the pending actions.
+     */
+    pkgMarkSchedule( PackageListView, pkgData->Schedule( action, pkgname ) );
+    UpdatePackageMenuBindings();
+  }
+}
+
+inline unsigned long pkgActionItem::CancelScheduledAction( void )
+{
+  /* Helper method to mark a scheduled action as "cancelled".
+   */
+  return (this != NULL) ? (flags &= ~ACTION_MASK) : 0UL;
+}
+
+void AppWindowMaker::UnmarkSelectedPackage( void )
+{
+  /* Method to clear any request for an action in respect of
+   * the currently selected package entry in the list view; we
+   * implement this as a cancellation of any pending scheduled
+   * action, in respect of the selected package.
+   *
+   * First, obtain a reference for the list view selection...
+   */
+  LVITEM lookup;
+  lookup.iItem = (PackageListView != NULL)
+    ? ListView_GetNextItem( PackageListView, (WPARAM)(-1), LVIS_SELECTED )
+    : -1;
+
+  /* ...and when it represents a valid selection...
+   */
+  if( lookup.iItem >= 0 )
+  {
+    /* ...retrieve its associated XML database package reference...
+     */
+    pkgXmlNode *pkg = pkgListSelection( PackageListView, &lookup );
+    /*
+     * ...search the action schedule, for an action associated with
+     * this package, if any, and cancel it.
+     */
+    pkgData->Schedule()->GetReference( pkg )->CancelScheduledAction();
+
+    /* The scheduling state for packages shown in the list view
+     * may have changed, so refresh the icon associations and the
+     * package menu bindings accordingly.
+     */
+    pkgMarkSchedule( PackageListView, pkgData->Schedule() );
+    UpdatePackageMenuBindings();
+  }
+}
+
 long AppWindowMaker::OnNotify( WPARAM client_id, LPARAM data )
 {
   /* Handler for notifiable events to be processed in the context
@@ -1021,4 +1273,65 @@ long AppWindowMaker::OnNotify( WPARAM client_id, LPARAM data )
   return EXIT_SUCCESS;
 }
 
+inline unsigned long pkgActionItem::Unapplied( void )
+{
+  /* Helper method to count the pending actions in a
+   * scheduled action list.
+   */
+  unsigned long count = 0;
+  if( this != NULL )
+  {
+    /* Assuming that the initial 'this' pointer is closer
+     * to the end of the list, than to the beginning...
+     */
+    pkgActionItem *item = this;
+    while( item->next != NULL )
+      /*
+       * ...advance, to ensure we have located the very
+       * last item in the schedule.
+       */
+      item = item->next;
+
+    /* Now, working back from end to beginning...
+     */
+    while( item != NULL )
+    {
+      /* ...note items with any scheduled action...
+       */
+      if( item->flags & ACTION_MASK )
+       /*
+        * ...and count them...
+        */
+       ++count;
+
+      /* ...then move on, to consider the preceding
+       * entry, if any.
+       */
+      item = item->prev;
+    }
+  }
+  /* Ultimately, return the count of pending actions,
+   * as noted while processing the above loop.
+   */
+  return count;
+}
+
+long AppWindowMaker::OnClose()
+{
+  /* Intercept application termination requests; check for
+   * outstanding pending actions, and offer a cancellation
+   * option for the termination request, so that the user
+   * has an opportunity to complete such actions.
+   */
+  if( (pkgData->Schedule()->Unapplied() > 0)
+  &&  (MessageBox( AppWindow,
+       "You have marked changes which have not been applied;\n"
+       "these will be lost, if you quit without applying them.\n\n"
+       "Are you sure you want to discard these marked changes?",
+       "Discard Marked Changes?", MB_YESNO | MB_ICONWARNING
+      ) == IDNO)
+    ) return 0;
+  return -1;
+}
+
 /* $RCSfile$: end of file */
index f6fd61a..751a876 100644 (file)
@@ -837,6 +837,56 @@ void pkgActionItem::ApplyBounds( pkgXmlNode *release, const char *bounds )
   }
 }
 
+pkgActionItem *pkgActionItem::GetReference( pkgXmlNode *package )
+{
+  /* Method to locate a scheduled action, if any, which relates
+   * to a specified package.
+   */
+  if( this != NULL )
+  {
+    /* The schedule of actions is not empty.  Assume that the
+     * initial 'this' pointer is closer to the end, than to the
+     * beginning of the list of scheduled actions, and...
+     */
+    pkgActionItem *item = this;
+    while( item->next != NULL )
+      /*
+       * ...advance, to locate the very last entry in the list.
+       */
+      item = item->next;
+
+    /* Now, working backward toward the beginning of the list...
+     */
+    while( item != NULL )
+    {
+      /* ...identify a "release" specification associated with
+       * each action item in turn...
+       */
+      pkgXmlNode *ref = item->Selection();
+      if( (ref != NULL) || ((ref = item->Selection( to_remove )) != NULL) )
+      {
+       /* ...convert this to an actual component package, or
+        * full package, reference...
+        */
+       while( ref->IsElementOfType( release_key ) )
+         ref = ref->GetParent();
+
+       /* ...and, if it matches the search target, return it.
+        */
+       if( ref == package ) return item;
+      }
+      /* ...or, when we haven't yet found a matching package,
+       * try the preceding scheduled action item, if any.
+       */
+      item = item->prev;
+    }
+  }
+  /* If we fall through to here, then we found no action to be
+   * performed on the specified package; report accordingly.
+   */
+  return NULL;
+}
+
 static void dmh_notify_no_match
 ( const char *name, pkgXmlNode *package, const char *bounds )
 {
@@ -869,251 +919,272 @@ static void dmh_notify_no_match
   dmh_control( DMH_END_DIGEST );
 }
 
-void pkgXmlDocument::Schedule( unsigned long action, const char* name )
+pkgActionItem* pkgXmlDocument::Schedule( unsigned long action, const char* name )
 {
   /* Task scheduler interface; schedules actions to process all
    * dependencies for the package specified by "name", honouring
    * any appended version bounds specified for the parent.
    */
-  char scratch_pad[strlen( name )];
-  const char *bounds_specification = get_version_bounds( name );
-  if( bounds_specification != NULL )
-  {
-    /* Separate any version bounds specification from
-     * the original package name specification.
+  if( this == NULL )
+    /*
+     * An unassigned XML database document cannot have any
+     * assigned action; bail out, with appropriate status.
      */
-    size_t scratch_pad_len = bounds_specification - name;
-    name = (const char *)(memcpy( scratch_pad, name, scratch_pad_len ));
-    scratch_pad[scratch_pad_len] = '\0';
-  }
+    return NULL;
 
-  pkgXmlNode *release;
-  if( (release = FindPackageByName( name )) != NULL )
+  /* We may call this method without any assigned package name,
+   * in which case, we interpret it as a request to return the
+   * list of previously scheduled actions...
+   */
+  if( name != NULL )
   {
-    /* We found the specification for the named package...
+    /* ...but when a package name is specified, then we
+     * proceed to schedule the specified action on it.
      */
-    pkgXmlNode *component = release->FindFirstAssociate( component_key );
-    if( component != NULL )
-      /*
-       * When it is subdivided into component-packages,
-       * we need to consider each as a possible candidate
-       * for task scheduling.
+    char scratch_pad[strlen( name )];
+    const char *bounds_specification = get_version_bounds( name );
+    if( bounds_specification != NULL )
+    {
+      /* Separate any version bounds specification from
+       * the original package name specification.
        */
-      release = component;
+      size_t scratch_pad_len = bounds_specification - name;
+      name = (const char *)(memcpy( scratch_pad, name, scratch_pad_len ));
+      scratch_pad[scratch_pad_len] = '\0';
+    }
 
-    while( release != NULL )
+    pkgXmlNode *release;
+    if( (release = FindPackageByName( name )) != NULL )
     {
-      /* Within each candidate package or component-package...
+      /* We found the specification for the named package...
        */
-      pkgXmlNode *package = release;
-      if( (release = release->FindFirstAssociate( release_key )) != NULL )
-      {
-       /* ...initially assume it is not installed, and that
-        * no installable upgrade is available.
-        */
-       pkgActionItem latest;
-       pkgXmlNode *installed = NULL, *upgrade = NULL;
-
-       /* Establish the action for which dependency resolution is
-        * to be performed; note that this may be promoted to a more
-        * inclusive class, during resolution, so we need to reset
-        * it for each new dependency which may be encountered.
-        */
-       request = action;
-
-       /* Any action request processed here is, by definition,
-        * a request for a primary action; mark it as such.
-        */
-       action |= ACTION_PRIMARY;
-
-       /* When the user has given a version bounds specification,
-        * then we must assign appropriate action item requirements.
+      pkgXmlNode *component = release->FindFirstAssociate( component_key );
+      if( component != NULL )
+       /*
+        * When it is subdivided into component-packages,
+        * we need to consider each as a possible candidate
+        * for task scheduling.
         */
-       if( bounds_specification != NULL )
-         latest.ApplyBounds( release, bounds_specification );
+       release = component;
 
-       /* For each candidate release in turn...
+      while( release != NULL )
+      {
+       /* Within each candidate package or component-package...
         */
-       while( release != NULL )
+       pkgXmlNode *package = release;
+       if( (release = release->FindFirstAssociate( release_key )) != NULL )
        {
-         /* ...inspect it to identify any which is already installed,
-          * and also the latest available...
+         /* ...initially assume it is not installed, and that
+          * no installable upgrade is available.
           */
-         if( is_installed( release ) )
-           /*
-            * ...i.e. here we have identified a release
-            * which is currently installed...
-            */
-           latest.SelectPackage( installed = release, to_remove );
+         pkgActionItem latest;
+         pkgXmlNode *installed = NULL, *upgrade = NULL;
 
-         if( latest.SelectIfMostRecentFit( release ) == release )
-           /*
-            * ...while this is the most recent we have
-            * encountered so far.
-            */
-           upgrade = release;
+         /* Establish the action for which dependency resolution is
+          * to be performed; note that this may be promoted to a more
+          * inclusive class, during resolution, so we need to reset
+          * it for each new dependency which may be encountered.
+          */
+         request = action;
 
-         /* Continue with the next specified release, if any.
+         /* Any action request processed here is, by definition,
+          * a request for a primary action; mark it as such.
           */
-         release = release->FindNextAssociate( release_key );
-       }
+         action |= ACTION_PRIMARY;
 
-       if( (installed = assert_installed( upgrade, installed )) == NULL )
-       {
-         /* There is no installed version...
-          * therefore, there is nothing to do for any action
-          * other than ACTION_INSTALL...
+         /* When the user has given a version bounds specification,
+          * then we must assign appropriate action item requirements.
           */
-         if( (action & ACTION_MASK) == ACTION_INSTALL )
+         if( bounds_specification != NULL )
+           latest.ApplyBounds( release, bounds_specification );
+
+         /* For each candidate release in turn...
+          */
+         while( release != NULL )
          {
-           /*
-            * ...in which case, we must recursively resolve
-            * any dependencies for the scheduled "upgrade".
-            */
-           if( latest.Selection() == NULL )
-             dmh_notify_no_match( name, package, bounds_specification );
-           else
-             ResolveDependencies(
-                 upgrade, Schedule( with_download( action ), latest )
-               );
-         }
-         else
-         { /* attempting ACTION_UPGRADE or ACTION_REMOVE
-            * is an error; diagnose it.
+           /* ...inspect it to identify any which is already installed,
+            * and also the latest available...
             */
-           if( component == NULL )
+           if( is_installed( release ) )
              /*
-              * In this case, the user explicitly specified a single
-              * package component, so it's a simple error...
+              * ...i.e. here we have identified a release
+              * which is currently installed...
               */
-             dmh_notify( DMH_ERROR, "%s %s: package is not installed\n",
-                 action_name( action & ACTION_MASK ), name
-               );
-           else
-           {
-             /* ...but here, the user specified only the package name,
-              * which implicitly applies to all associated components;
-              * since some may be installed, prefer to issue a warning
-              * in respect of any which aren't.
+             latest.SelectPackage( installed = release, to_remove );
+
+           if( latest.SelectIfMostRecentFit( release ) == release )
+             /*
+              * ...while this is the most recent we have
+              * encountered so far.
               */
-             const char *extname = component->GetPropVal( class_key, "" );
-             char full_package_name[2 + strlen( name ) + strlen( extname )];
-             sprintf( full_package_name, *extname ? "%s-%s" : "%s", name, extname );
+             upgrade = release;
 
-             dmh_control( DMH_BEGIN_DIGEST );
-             dmh_notify( DMH_WARNING, "%s %s: request ignored...\n",
-                 extname = action_name( action & ACTION_MASK ), full_package_name
-               );
-             dmh_notify( DMH_WARNING, "%s: package was not previously installed\n",
-                 full_package_name
-               );
-             dmh_notify( DMH_WARNING, "%s: it will remain this way until you...\n",
-                 full_package_name
-               );
-             dmh_notify( DMH_WARNING, "use 'mingw-get install %s' to install it\n",
-                 full_package_name
-               );
-             dmh_control( DMH_END_DIGEST );
-           }
-         }
-       }
-       else if( upgrade && (upgrade != installed) )
-       {
-         /* There is an installed version, but an upgrade to a newer
-          * version is available; when performing ACTION_UPGRADE...
-          */
-         if( (action & ACTION_MASK) == ACTION_UPGRADE )
-           /*
-            * ...we must recursively resolve any dependencies...
+           /* Continue with the next specified release, if any.
             */
-           ResolveDependencies( upgrade,
-               Schedule( with_download( action ), latest )
-             );
+           release = release->FindNextAssociate( release_key );
+         }
 
-         else if( (action & ACTION_MASK) == ACTION_REMOVE )
+         if( (installed = assert_installed( upgrade, installed )) == NULL )
          {
-           /* ...while for ACTION_REMOVE, we have little to do,
-            * beyond scheduling the removal; (we don't extend the
-            * scope of a remove request to prerequisite packages,
-            * so there is no need to resolve dependencies)...
-            */
-           latest.SelectPackage( installed );
-           Schedule( action, latest );
-         }
-         else
-         { /* ...but, we decline to proceed with ACTION_INSTALL
-            * unless the --reinstall option is enabled...
+           /* There is no installed version...
+            * therefore, there is nothing to do for any action
+            * other than ACTION_INSTALL...
             */
-           if( pkgOptions()->Test( OPTION_REINSTALL ) )
+           if( (action & ACTION_MASK) == ACTION_INSTALL )
            {
-             /* ...in which case, we resolve dependencies for,
-              * and reschedule a reinstallation of the currently
-              * installed version...
+             /*
+              * ...in which case, we must recursively resolve
+              * any dependencies for the scheduled "upgrade".
               */
-             latest.SelectPackage( installed );
-             ResolveDependencies( installed,
-                 Schedule( with_download( action | ACTION_REMOVE ), latest )
-               );
+             if( latest.Selection() == NULL )
+               dmh_notify_no_match( name, package, bounds_specification );
+             else
+               ResolveDependencies(
+                   upgrade, Schedule( with_download( action ), latest )
+                 );
            }
            else
-           { /* ...otherwise, we reformulate the appropriate
-              * fully qualified package name...
+           { /* attempting ACTION_UPGRADE or ACTION_REMOVE
+              * is an error; diagnose it.
               */
-             const char *extname = ( component != NULL )
-               ? component->GetPropVal( class_key, "" )
-               : "";
-             char full_package_name[2 + strlen( name ) + strlen( extname )];
-             sprintf( full_package_name, *extname ? "%s-%s" : "%s", name, extname );
+             if( component == NULL )
+               /*
+                * In this case, the user explicitly specified a single
+                * package component, so it's a simple error...
+                */
+               dmh_notify( DMH_ERROR, "%s %s: package is not installed\n",
+                   action_name( action & ACTION_MASK ), name
+                 );
+             else
+             {
+               /* ...but here, the user specified only the package name,
+                * which implicitly applies to all associated components;
+                * since some may be installed, prefer to issue a warning
+                * in respect of any which aren't.
+                */
+               const char *extname = component->GetPropVal( class_key, "" );
+               char full_package_name[2 + strlen( name ) + strlen( extname )];
+               sprintf( full_package_name, *extname ? "%s-%s" : "%s", name, extname );
+
+               dmh_control( DMH_BEGIN_DIGEST );
+               dmh_notify( DMH_WARNING, "%s %s: request ignored...\n",
+                   extname = action_name( action & ACTION_MASK ), full_package_name
+                 );
+               dmh_notify( DMH_WARNING, "%s: package was not previously installed\n",
+                   full_package_name
+                 );
+               dmh_notify( DMH_WARNING, "%s: it will remain this way until you...\n",
+                   full_package_name
+                 );
+               dmh_notify( DMH_WARNING, "use 'mingw-get install %s' to install it\n",
+                   full_package_name
+                 );
+               dmh_control( DMH_END_DIGEST );
+             }
+           }
+         }
+         else if( upgrade && (upgrade != installed) )
+         {
+           /* There is an installed version, but an upgrade to a newer
+            * version is available; when performing ACTION_UPGRADE...
+            */
+           if( (action & ACTION_MASK) == ACTION_UPGRADE )
              /*
-              * ...which we then incorporate into an advisory
-              * diagnostic message, which serves both to inform
-              * the user of this error condition, and also to
-              * suggest appropriate corrective action.
+              * ...we must recursively resolve any dependencies...
               */
-             dmh_control( DMH_BEGIN_DIGEST );
-             dmh_notify( DMH_ERROR, "%s: package is already installed\n",
-                 full_package_name
-               );
-             dmh_notify( DMH_ERROR, "use 'mingw-get upgrade %s' to upgrade it\n",
-                 full_package_name
-               );
-             dmh_notify( DMH_ERROR, "or 'mingw-get install --reinstall %s'\n",
-                 full_package_name
-               );
-             dmh_notify( DMH_ERROR, "to reinstall the currently installed version\n" 
+             ResolveDependencies( upgrade,
+                 Schedule( with_download( action ), latest )
                );
-             dmh_control( DMH_END_DIGEST );
+
+           else if( (action & ACTION_MASK) == ACTION_REMOVE )
+           {
+             /* ...while for ACTION_REMOVE, we have little to do,
+              * beyond scheduling the removal; (we don't extend the
+              * scope of a remove request to prerequisite packages,
+              * so there is no need to resolve dependencies)...
+              */
+             latest.SelectPackage( installed );
+             Schedule( action, latest );
+           }
+           else
+           { /* ...but, we decline to proceed with ACTION_INSTALL
+              * unless the --reinstall option is enabled...
+              */
+             if( pkgOptions()->Test( OPTION_REINSTALL ) )
+             {
+               /* ...in which case, we resolve dependencies for,
+                * and reschedule a reinstallation of the currently
+                * installed version...
+                */
+               latest.SelectPackage( installed );
+               ResolveDependencies( installed,
+                   Schedule( with_download( action | ACTION_REMOVE ), latest )
+                 );
+             }
+             else
+             { /* ...otherwise, we reformulate the appropriate
+                * fully qualified package name...
+                */
+               const char *extname = ( component != NULL )
+                 ? component->GetPropVal( class_key, "" )
+                 : "";
+               char full_package_name[2 + strlen( name ) + strlen( extname )];
+               sprintf( full_package_name, *extname ? "%s-%s" : "%s", name, extname );
+               /*
+                * ...which we then incorporate into an advisory
+                * diagnostic message, which serves both to inform
+                * the user of this error condition, and also to
+                * suggest appropriate corrective action.
+                */
+               dmh_control( DMH_BEGIN_DIGEST );
+               dmh_notify( DMH_ERROR, "%s: package is already installed\n",
+                   full_package_name
+                 );
+               dmh_notify( DMH_ERROR, "use 'mingw-get upgrade %s' to upgrade it\n",
+                   full_package_name
+                 );
+               dmh_notify( DMH_ERROR, "or 'mingw-get install --reinstall %s'\n",
+                   full_package_name
+                 );
+               dmh_notify( DMH_ERROR, "to reinstall the currently installed version\n" 
+                 );
+               dmh_control( DMH_END_DIGEST );
+             }
            }
          }
-       }
-       else
-       { /* In this case, the package is already installed,
-          * and no more recent release is available; we still
-          * recursively resolve its dependencies, to capture
-          * any potential upgrades for them.
-          */
-         if( latest.Selection() == NULL )
-           dmh_notify_no_match( name, package, bounds_specification );
          else
-           ResolveDependencies( upgrade, Schedule( action, latest ));
+         { /* In this case, the package is already installed,
+            * and no more recent release is available; we still
+            * recursively resolve its dependencies, to capture
+            * any potential upgrades for them.
+            */
+           if( latest.Selection() == NULL )
+             dmh_notify_no_match( name, package, bounds_specification );
+           else
+             ResolveDependencies( upgrade, Schedule( action, latest ));
+         }
        }
-      }
 
-      if( (component = component->FindNextAssociate( component_key )) != NULL )
-       /*
-        * When evaluating a component-package, we extend our
-        * evaluation, to consider for any further components of
-        * the current package.
-        */
-       release = component;
+       if( (component = component->FindNextAssociate( component_key )) != NULL )
+         /*
+          * When evaluating a component-package, we extend our
+          * evaluation, to consider for any further components of
+          * the current package.
+          */
+         release = component;
+      }
     }
-  }
 
-  else
-    /* We found no information on the requested package;
-     * diagnose as a non-fatal error.
-     */
-    dmh_notify( DMH_ERROR, pkgMsgUnknownPackage(), name );
+    else
+      /* We found no information on the requested package;
+       * diagnose as a non-fatal error.
+       */
+      dmh_notify( DMH_ERROR, pkgMsgUnknownPackage(), name );
+  }
+  /* Finally, we return a pointer to the currently scheduled
+   * actions list, if any.
+   */
+  return actions;
 }
 
 void pkgXmlDocument::RescheduleInstalledPackages( unsigned long action )