6 * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7 * Copyright (C) 2009, 2010, 2011, 2012, MinGW.org Project
10 * Implementation of repository binding for the pkgXmlDocument class.
13 * This is free software. Permission is granted to copy, modify and
14 * redistribute this software, under the provisions of the GNU General
15 * Public License, Version 3, (or, at your option, any later version),
16 * as published by the Free Software Foundation; see the file COPYING
17 * for licensing details.
19 * Note, in particular, that this software is provided "as is", in the
20 * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
21 * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
22 * PARTICULAR PURPOSE. Under no circumstances will the author, or the
23 * MinGW Project, accept liability for any damages, however caused,
24 * arising from the use of this software.
39 /* A locally defined class to facilitate recursive retrieval
40 * of package lists, from any specified repository.
43 static void Reset( void ){ count = total = 0; }
44 static void IncrementTotal( void ){ ++total; }
46 pkgRepository( pkgXmlDocument*, pkgXmlNode*, pkgXmlNode*, bool );
49 void GetPackageList( const char* );
50 void GetPackageList( pkgXmlNode* );
54 pkgXmlNode *repository;
55 pkgXmlDocument *owner;
56 const char *expected_issue;
57 static int count, total;
61 /* Don't forget that we MUST explicitly allocate static storage for
62 * static property values declared within the pkgRepository class.
64 int pkgRepository::count;
65 int pkgRepository::total;
67 /* The relative age of catalogue files is determined by alpha-numeric
68 * lexical comparison of a ten digit "issue number" string; internally,
69 * we use a "pseudo issue number" represented as "XXXXXXXXXX", when we
70 * wish to pre-emptively assume that the repository may serve a newer
71 * version of any catalogue which is already present locally; (users
72 * may specify "ZZZZZZZZZZ" to override such assumptions).
74 * Here, we define the string representing assumed newness.
76 static const char *value_assumed_new = "XXXXXXXXXX";
78 pkgRepository::pkgRepository
82 ( pkgXmlDocument *client, pkgXmlNode *db, pkgXmlNode *ref, bool mode ):
83 owner( client ), dbase( db ), repository( ref ), force_update( mode ),
84 expected_issue( value_assumed_new ){}
86 /* Provide the hook, via which the package group hierarchy builder
87 * may gain access to its configuration data, during loading of the
90 pkgXmlNode::GroupHierarchyMapper
91 pkgXmlNode::PackageGroupHierarchyMapper = NULL;
92 inline void pkgXmlNode::MapPackageGroupHierarchy( pkgXmlNode *catalogue )
94 /* This is a no-op, unless the client attaches a handler to the
95 * hook, before invoking the package list loader.
97 if( PackageGroupHierarchyMapper != NULL )
98 PackageGroupHierarchyMapper( this, catalogue );
101 void pkgRepository::GetPackageList( const char *dname )
103 /* Helper to retrieve and recursively process a named package list.
105 * FIXME: having made this recursively process multiple catalogues,
106 * potentially from multiple independent repositories, we may have
107 * introduced potential for catalogue name clashes; we need to add
108 * name hashing in the local catalogue cache, to avoid conflicts.
113 if( (dfile = xmlfile( dname )) != NULL )
115 /* We've identified a further "package-list" file; update the
116 * count of such files processed, to include this one.
120 /* Set up diagnostics for reporting catalogue loading progress.
122 const char *mode = force_update ? "Checking" : "Loading";
123 const char *fmt = (owner->ProgressMeter() == NULL)
124 ? "%s catalogue: %s.xml; (item %d of %d)\n"
125 : "%s catalogue: %s.xml\n";
127 /* Check for a locally cached copy of the "package-list" file...
129 const char *current_issue;
130 if( ((current_issue = serial_number( dfile )) == NULL)
132 * ...and, when present, make a pre-emptive assessment of any
133 * necessity to download and update to a newer version.
135 || (force_update && (strcmp( current_issue, expected_issue ) < 0)) )
137 /* Once we've tested it, for possible availability of a more
138 * recent issue, we have no further need to refer to the issue
139 * number of the currently cached catalogue.
141 free( (void *)(current_issue) );
143 /* When performing an "update", or if no local copy is available...
144 * Force a "sync", to fetch a copy from the public host.
146 const char *mode = force_update ? "Updating" : "Downloading";
147 if( owner->ProgressMeter() != NULL )
149 * Progress of the "update" is being metered; annotate the
150 * metering display accordingly...
152 owner->ProgressMeter()->Annotate( fmt, mode, dname, count, total );
155 /* Progress is not being explicitly metered, but the user
156 * may still appreciate a minimal progress report...
158 dmh_printf( fmt, mode, dname, count, total );
160 /* During the actual fetch, collect any generated diagnostics
161 * for the current catalogue file into a message digest, so
162 * that the GUI may present them in a single message box.
164 dmh_control( DMH_BEGIN_DIGEST );
165 owner->SyncRepository( dname, repository );
167 else if( owner->ProgressMeter() != NULL )
169 * This is a simple request to load a local copy of the
170 * catalogue file; progress metering is in effect, so we
171 * annotate the metering display accordingly...
173 owner->ProgressMeter()->Annotate( fmt, mode, dname, count, total );
175 else if( force_update || (pkgOptions()->Test( OPTION_VERBOSE ) > 1) )
177 * Similarly, this is a request to load a local copy of
178 * the catalogue; progress metering is not in effect, but
179 * the user has requested either an update when there was
180 * none available, or verbose diagnostics but no update,
181 * so issue a diagnostic progress report.
183 dmh_printf( fmt, mode, dname, count, total );
185 /* We SHOULD now have a locally cached copy of the package-list;
186 * attempt to merge it into the active profile database...
188 pkgXmlDocument merge( dfile );
191 /* We successfully loaded the XML catalogue; refer to its
194 pkgXmlNode *catalogue, *pkglist;
195 if( (catalogue = merge.GetRoot()) != NULL )
197 /* ...map any package group hierarchy which it specifies...
199 dbase->MapPackageGroupHierarchy( catalogue );
201 /* ...then read it, selecting each of the "package-collection"
202 * records contained within it...
204 pkglist = catalogue->FindFirstAssociate( package_collection_key );
205 while( pkglist != NULL )
207 /* ...and append a copy of each to the active profile...
209 dbase->LinkEndChild( pkglist->Clone() );
211 /* Move on to the next "package-collection" (if any)
212 * within the current catalogue...
214 pkglist = pkglist->FindNextAssociate( package_collection_key );
217 /* Recursively incorporate any additional package lists,
218 * which may be specified within the current catalogue...
220 catalogue = catalogue->FindFirstAssociate( package_list_key );
221 if( (pkglist = catalogue) != NULL )
223 /* ...updating the total catalogue reference count,
224 * to include all extra catalogue files specified.
227 pkglist = pkglist->FindNextAssociate( package_list_key );
228 } while( pkglist != NULL );
230 /* Flush any message digest which has been accumulated
231 * for the last catalogue processed...
233 dmh_control( DMH_END_DIGEST );
234 if( owner->ProgressMeter() != NULL )
236 /* ...and update the progress meter display, if any,
237 * to reflect current progress.
239 owner->ProgressMeter()->SetRange( 0, total );
240 owner->ProgressMeter()->SetValue( count );
242 /* Proceed to process the embedded catalogues.
244 GetPackageList( catalogue );
248 { /* The specified catalogue could not be successfully loaded;
249 * emit a warning diagnostic message, and otherwise ignore it.
251 dmh_notify( DMH_WARNING, "Load catalogue: FAILED: %s.xml\n", dname );
254 /* However we handled it, the XML file's path name in "dfile" was
255 * allocated on the heap; we lose its reference on termination of
256 * this loop, so we must free it to avoid a memory leak.
258 free( (void *)(dfile) );
261 /* Ensure that any accumulated diagnostics, pertaining to catalogue
262 * processing, have been displayed before wrapping up.
264 dmh_control( DMH_END_DIGEST );
267 void pkgRepository::GetPackageList( pkgXmlNode *catalogue )
269 /* Helper method to retrieve a set of package list specifications
270 * from a "package-list" catalogue; after processing the specified
271 * "catalogue" it iterates over any sibling XML elements which are
272 * also designated as being of the "package-list" type.
274 * Note: we assume that the passed catalogue element actually
275 * DOES represent a "package-list" element; we do not check this,
276 * because the class declaration is not exposed externally to this
277 * translation unit, and we only ever call this from within the
278 * unit, when we have a "package-list" element to process.
280 while( catalogue != NULL )
282 /* Evaluate each identified "package-list" catalogue in turn...
284 expected_issue = catalogue->GetPropVal( issue_key, value_assumed_new );
285 GetPackageList( catalogue->GetPropVal( catalogue_key, NULL ) );
287 /* A repository may comprise an arbitrary collection of software
288 * catalogues; move on, to process the next catalogue (if any) in
289 * the current repository collection.
291 catalogue = catalogue->FindNextAssociate( package_list_key );
295 pkgXmlNode *pkgXmlDocument::BindRepositories( bool force_update )
297 /* Identify the repositories specified in the application profile,
298 * and merge their associated package distribution lists into the
299 * active XML database, which is bound to the profile.
301 pkgXmlNode *dbase = GetRoot();
303 /* Before blindly proceeding, perform a sanity check...
304 * Verify that this XML database defines an application profile,
305 * and that the associated application is "mingw-get"...
307 if( (strcmp( dbase->GetName(), profile_key ) == 0)
308 && (strcmp( dbase->GetPropVal( application_key, "?" ), "mingw-get") == 0) )
310 /* Sanity check passed...
311 * Walk the XML data tree, selecting "repository" specifications...
313 pkgRepository::Reset();
314 pkgXmlNode *repository = dbase->FindFirstAssociate( repository_key );
315 while( repository != NULL )
317 /* For each "repository" specified, identify its "catalogues"...
319 pkgRepository client( this, dbase, repository, force_update );
320 pkgXmlNode *catalogue = repository->FindFirstAssociate( package_list_key );
321 if( catalogue == NULL )
323 /* This repository specification doesn't identify any named
324 * package list, so try the default, (which is named to match
325 * the XML key name for the "package-list" element)...
327 pkgRepository::IncrementTotal();
328 client.GetPackageList( package_list_key );
331 { /* At least one package list catalogue is specified; load it,
332 * and any others which are explicitly identified...
334 pkgXmlNode *ref = catalogue;
335 do { pkgRepository::IncrementTotal();
336 ref = ref->FindNextAssociate( package_list_key );
337 } while( ref != NULL );
339 client.GetPackageList( catalogue );
342 /* Similarly, a complete distribution may draw from an arbitrary set
343 * of distinct repositories; move on, to process the next repository
344 * specified (if any).
346 repository = repository->FindNextAssociate( repository_key );
349 /* On successful completion, return a pointer to the root node
350 * of the active XML profile.
355 /* Fall through on total failure to interpret the profile, returning
356 * NULL to indicate failure.
361 /* $RCSfile$: end of file */