OSDN Git Service

Support group affiliation with component package granularity.
[mingw/mingw-get.git] / src / pkgbind.cpp
1 /*
2  * pkgbind.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2009, 2010, 2011, 2012, MinGW.org Project
8  *
9  *
10  * Implementation of repository binding for the pkgXmlDocument class.
11  *
12  *
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.
18  *
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.
25  *
26  */
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #include "dmh.h"
33 #include "pkgbase.h"
34 #include "pkgkeys.h"
35 #include "pkgopts.h"
36
37 class pkgRepository
38 {
39   /* A locally defined class to facilitate recursive retrieval
40    * of package lists, from any specified repository.
41    */
42   public:
43     static void Reset( void ){ count = total = 0; }
44     static void IncrementTotal( void ){ ++total; }
45
46     pkgRepository( pkgXmlDocument*, pkgXmlNode*, pkgXmlNode*, bool );
47     ~pkgRepository(){};
48
49     void GetPackageList( const char* );
50     void GetPackageList( pkgXmlNode* );
51
52   private:
53     pkgXmlNode *dbase;
54     pkgXmlNode *repository;
55     pkgXmlDocument *owner;
56     const char *expected_issue;
57     static int count, total;
58     bool force_update;
59 };
60
61 /* Don't forget that we MUST explicitly allocate static storage for
62  * static property values declared within the pkgRepository class.
63  */
64 int pkgRepository::count;
65 int pkgRepository::total;
66
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).
73  *
74  * Here, we define the string representing assumed newness.
75  */
76 static const char *value_assumed_new = "XXXXXXXXXX";
77
78 pkgRepository::pkgRepository
79 /*
80  * Constructor...
81  */
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 ){}
85
86 /* Provide the hook, via which the package group hierarchy builder
87  * may gain access to its configuration data, during loading of the
88  * package list files.
89  */
90 pkgXmlNode::GroupHierarchyMapper
91 pkgXmlNode::PackageGroupHierarchyMapper = NULL;
92 inline void pkgXmlNode::MapPackageGroupHierarchy( pkgXmlNode *catalogue )
93 {
94   /* This is a no-op, unless the client attaches a handler to the
95    * hook, before invoking the package list loader.
96    */
97   if( PackageGroupHierarchyMapper != NULL )
98     PackageGroupHierarchyMapper( this, catalogue );
99 }
100
101 void pkgRepository::GetPackageList( const char *dname )
102 {
103   /* Helper to retrieve and recursively process a named package list.
104    *
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.
109    */
110   if( dname != NULL )
111   {
112     const char *dfile;
113     if( (dfile = xmlfile( dname )) != NULL )
114     {
115       /* We've identified a further "package-list" file; update the
116        * count of such files processed, to include this one.
117        */
118       ++count;
119
120       /* Set up diagnostics for reporting catalogue loading progress.
121        */
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";
126
127       /* Check for a locally cached copy of the "package-list" file...
128        */
129       const char *current_issue;
130       if(  ((current_issue = serial_number( dfile )) == NULL)
131       /*
132        * ...and, when present, make a pre-emptive assessment of any
133        * necessity to download and update to a newer version.
134        */
135       ||  (force_update && (strcmp( current_issue, expected_issue ) < 0))  )
136       {
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.
140          */
141         free( (void *)(current_issue) );
142
143         /* When performing an "update", or if no local copy is available...
144          * Force a "sync", to fetch a copy from the public host.
145          */
146         const char *mode = force_update ? "Updating" : "Downloading";
147         if( owner->ProgressMeter() != NULL )
148           /*
149            * Progress of the "update" is being metered; annotate the
150            * metering display accordingly...
151            */
152           owner->ProgressMeter()->Annotate( fmt, mode, dname, count, total );
153
154         else
155           /* Progress is not being explicitly metered, but the user
156            * may still appreciate a minimal progress report...
157            */
158           dmh_printf( fmt, mode, dname, count, total );
159
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.
163          */
164         dmh_control( DMH_BEGIN_DIGEST );
165         owner->SyncRepository( dname, repository );
166       }
167       else if( owner->ProgressMeter() != NULL )
168         /*
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...
172          */
173         owner->ProgressMeter()->Annotate( fmt, mode, dname, count, total );
174
175       else if( force_update || (pkgOptions()->Test( OPTION_VERBOSE ) > 1) )
176         /*
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.
182          */
183         dmh_printf( fmt, mode, dname, count, total );
184
185       /* We SHOULD now have a locally cached copy of the package-list;
186        * attempt to merge it into the active profile database...
187        */
188       pkgXmlDocument merge( dfile );
189       if( merge.IsOk() )
190       {
191         /* We successfully loaded the XML catalogue; refer to its
192          * root element...
193          */
194         pkgXmlNode *catalogue, *pkglist;
195         if( (catalogue = merge.GetRoot()) != NULL )
196         {
197           /* ...map any package group hierarchy which it specifies...
198            */
199           dbase->MapPackageGroupHierarchy( catalogue );
200
201           /* ...then read it, selecting each of the "package-collection"
202            * records contained within it...
203            */
204           pkglist = catalogue->FindFirstAssociate( package_collection_key );
205           while( pkglist != NULL )
206           {
207             /* ...and append a copy of each to the active profile...
208              */
209             dbase->LinkEndChild( pkglist->Clone() );
210
211             /* Move on to the next "package-collection" (if any)
212              * within the current catalogue...
213              */
214             pkglist = pkglist->FindNextAssociate( package_collection_key );
215           }
216
217           /* Recursively incorporate any additional package lists,
218            * which may be specified within the current catalogue...
219            */
220           catalogue = catalogue->FindFirstAssociate( package_list_key );
221           if( (pkglist = catalogue) != NULL )
222             do {
223                  /* ...updating the total catalogue reference count,
224                   * to include all extra catalogue files specified.
225                   */
226                  ++total;
227                  pkglist = pkglist->FindNextAssociate( package_list_key );
228                } while( pkglist != NULL );
229
230           /* Flush any message digest which has been accumulated
231            * for the last catalogue processed...
232            */
233           dmh_control( DMH_END_DIGEST );
234           if( owner->ProgressMeter() != NULL )
235           {
236             /* ...and update the progress meter display, if any,
237              * to reflect current progress.
238              */
239             owner->ProgressMeter()->SetRange( 0, total );
240             owner->ProgressMeter()->SetValue( count );
241           }
242           /* Proceed to process the embedded catalogues.
243            */
244           GetPackageList( catalogue );
245         }
246       }
247       else
248       { /* The specified catalogue could not be successfully loaded;
249          * emit a warning diagnostic message, and otherwise ignore it.
250          */
251         dmh_notify( DMH_WARNING, "Load catalogue: FAILED: %s.xml\n", dname );
252       }
253
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.
257        */
258       free( (void *)(dfile) );
259     }
260   }
261   /* Ensure that any accumulated diagnostics, pertaining to catalogue
262    * processing, have been displayed before wrapping up.
263    */
264   dmh_control( DMH_END_DIGEST );
265 }
266
267 void pkgRepository::GetPackageList( pkgXmlNode *catalogue )
268 {
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.
273    *
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.
279    */
280   while( catalogue != NULL )
281   {
282     /* Evaluate each identified "package-list" catalogue in turn...
283      */
284     expected_issue = catalogue->GetPropVal( issue_key, value_assumed_new );
285     GetPackageList( catalogue->GetPropVal( catalogue_key, NULL ) );
286
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.
290      */
291     catalogue = catalogue->FindNextAssociate( package_list_key );
292   }
293 }
294
295 pkgXmlNode *pkgXmlDocument::BindRepositories( bool force_update )
296 {
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.
300    */
301   pkgXmlNode *dbase = GetRoot();
302
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"...
306    */
307   if( (strcmp( dbase->GetName(), profile_key ) == 0)
308   &&  (strcmp( dbase->GetPropVal( application_key, "?" ), "mingw-get") == 0) )
309   {
310     /* Sanity check passed...
311      * Walk the XML data tree, selecting "repository" specifications...
312      */
313     pkgRepository::Reset();
314     pkgXmlNode *repository = dbase->FindFirstAssociate( repository_key );
315     while( repository != NULL )
316     {
317       /* For each "repository" specified, identify its "catalogues"...
318        */
319       pkgRepository client( this, dbase, repository, force_update );
320       pkgXmlNode *catalogue = repository->FindFirstAssociate( package_list_key );
321       if( catalogue == NULL )
322       {
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)...
326          */
327         pkgRepository::IncrementTotal();
328         client.GetPackageList( package_list_key );
329       }
330       else
331       { /* At least one package list catalogue is specified; load it,
332          * and any others which are explicitly identified...
333          */
334         pkgXmlNode *ref = catalogue;
335         do { pkgRepository::IncrementTotal();
336              ref = ref->FindNextAssociate( package_list_key );
337            } while( ref != NULL );
338
339         client.GetPackageList( catalogue );
340       }
341
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).
345        */
346       repository = repository->FindNextAssociate( repository_key );
347     }
348
349     /* On successful completion, return a pointer to the root node
350      * of the active XML profile.
351      */
352     return dbase;
353   }
354
355   /* Fall through on total failure to interpret the profile, returning
356    * NULL to indicate failure.
357    */
358   return NULL;
359 }
360
361 /* $RCSfile$: end of file */