From: Keith Marshall Date: Fri, 16 Nov 2012 16:52:48 +0000 (+0000) Subject: Add GUI support for install, upgrade, and remove action scheduling. X-Git-Tag: r0-6-0-beta-20130904-1~49^2~33 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=10ec2c8d892e5d557b7d0e6328f0a8cca5c6f10b;p=mingw%2Fmingw-get.git Add GUI support for install, upgrade, and remove action scheduling. --- diff --git a/ChangeLog b/ChangeLog index a786cb0..48b3965 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,45 @@ +2012-11-16 Keith Marshall + + 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 Add dynamically enabled state control for package menu items. diff --git a/src/guimain.h b/src/guimain.h index 8f7350f..0b5cce0 100644 --- a/src/guimain.h +++ b/src/guimain.h @@ -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 */ diff --git a/src/guixmld.cpp b/src/guixmld.cpp index cf9112c..3cd6e64 100644 --- a/src/guixmld.cpp +++ b/src/guixmld.cpp @@ -27,6 +27,7 @@ #include "guimain.h" #include "pkgbase.h" #include "pkgkeys.h" +#include "pkgtask.h" #include #include @@ -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 ); diff --git a/src/pkgbase.h b/src/pkgbase.h index 4cd1956..f85e716 100644 --- a/src/pkgbase.h +++ b/src/pkgbase.h @@ -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 ); diff --git a/src/pkgdata.cpp b/src/pkgdata.cpp index 036a193..f158735 100644 --- a/src/pkgdata.cpp +++ b/src/pkgdata.cpp @@ -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 */ diff --git a/src/pkgdeps.cpp b/src/pkgdeps.cpp index f6fd61a..751a876 100644 --- a/src/pkgdeps.cpp +++ b/src/pkgdeps.cpp @@ -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 )