OSDN Git Service

Add more protection against NULL pointer abuse.
[mingw/mingw-get.git] / src / pkglist.cpp
1 /*
2  * pkglist.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2012, MinGW Project
8  *
9  *
10  * Implementation of the methods for the pkgListViewMaker class, and
11  * its AppWindowMaker client API, to support the display of the package
12  * list in the mingw-get graphical user interface.
13  *
14  *
15  * This is free software.  Permission is granted to copy, modify and
16  * redistribute this software, under the provisions of the GNU General
17  * Public License, Version 3, (or, at your option, any later version),
18  * as published by the Free Software Foundation; see the file COPYING
19  * for licensing details.
20  *
21  * Note, in particular, that this software is provided "as is", in the
22  * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
23  * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
24  * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
25  * MinGW Project, accept liability for any damages, however caused,
26  * arising from the use of this software.
27  *
28  */
29 #define _WIN32_IE  0x0300
30
31 #include "guimain.h"
32 #include "pkgbase.h"
33 #include "pkglist.h"
34 #include "pkgtask.h"
35 #include "pkgkeys.h"
36 #include "pkginfo.h"
37
38 #include <wtkexcept.h>
39
40 void AppWindowMaker::InitPackageListView()
41 {
42   /* Create and initialise a ListView window, in which to present
43    * the package list...
44    */
45   PackageListView = CreateWindow( WC_LISTVIEW, NULL,
46       WS_VISIBLE | WS_BORDER | WS_CHILD | WS_EX_CLIENTEDGE |
47       LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS, 0, 0, 0, 0,
48       AppWindow, (HMENU)(ID_PACKAGE_LISTVIEW),
49       AppInstance, NULL
50     );
51   /* ...and set its extended style attributes.
52    */
53   ListView_SetExtendedListViewStyle( PackageListView,
54       LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT | LVS_EX_ONECLICKACTIVATE
55     );
56
57   /* Propagate the default font, as set for the main window itself,
58    * to the list view control.
59    */
60   SendMessage( PackageListView, WM_SETFONT, (WPARAM)(DefaultFont), TRUE );
61
62   /* Assign an image list, to furnish the package status icons.
63    */
64   HIMAGELIST iconlist = ImageList_Create( 16, 16, ILC_COLOR32 | ILC_MASK, 13, 0 );
65   for( int index = 0; index <= PKGSTATE( PURGE ); index++ )
66   {
67     HICON image = LoadIcon( AppInstance, MAKEINTRESOURCE(ID_PKGSTATE(index)) );
68     if( ImageList_AddIcon( iconlist, image ) == -1 )
69       throw WTK::runtime_error( "Error loading package status icons" );
70   }
71   ListView_SetImageList( PackageListView, iconlist, LVSIL_SMALL );
72
73   /* Initialise the table layout, and assign column headings.
74    */
75   LVCOLUMN table;
76   struct { int id; int width; char *text; } headings[] =
77   {
78     /* Specify the column headings for the package list table.
79      */
80     { ID_PKGLIST_TABLE_HEADINGS,  20, ""  },
81     { ID_PKGNAME_COLUMN_HEADING, 150, "Package" },
82     { ID_PKGTYPE_COLUMN_HEADING,  48, "Class" },
83     { ID_INSTVER_COLUMN_HEADING, 125, "Installed Version" },
84     { ID_REPOVER_COLUMN_HEADING, 125, "Repository Version" },
85     { ID_PKGDESC_COLUMN_HEADING, 400, "Description" },
86     /*
87      * This all-null entry terminates the sequence.
88      */
89     { 0, 0, NULL }
90   };
91   table.fmt = LVCFMT_LEFT;
92   table.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
93   for( int index = 0; headings[index].text != NULL; index++ )
94   {
95     /* Loop over the columns, setting the initial width
96      * (in pixels), and assigning the heading to each.
97      */
98     table.cx = headings[index].width;
99     table.pszText = headings[index].text;
100     ListView_InsertColumn( PackageListView, index, &table );
101   }
102   /* "Update" the package list, to initialise the list view.
103    */
104   UpdatePackageList();
105 }
106
107 void AppWindowMaker::UpdatePackageList()
108 {
109   /* Scan the XML package catalogue, adding a ListView item entry
110    * for each package record.
111    */
112   pkgListViewMaker PackageList( PackageListView );
113   pkgDirectory *dir = pkgData->CatalogueAllPackages();
114   dir->InOrder( &PackageList );
115   delete dir;
116 }
117
118 static char *pkgGetTitle( pkgXmlNode *pkg, const pkgXmlNode *xml_root )
119 {
120   /* A private helper method, to retrieve the title attribute
121    * associated with the description for the nominated package.
122    *
123    * Note: this should really return a const char *, but then
124    * we would need to cast it to non-const for mapping into the
125    * ill-formed structure of Microsoft's LVITEM, so we may just
126    * as well return the non-const result anyway.
127    */
128   pkgXmlNode *desc = pkg->FindFirstAssociate( description_key );
129   while( desc != NULL )
130   {
131     /* Handling it internally as the const which it should be...
132      */
133     const char *title;
134     if( (title = desc->GetPropVal( title_key, NULL )) != NULL )
135       /*
136        * As soon as we find a description element with an
137        * assigned title attribute, immediately return it,
138        * (with the required cast to non-const).
139        */
140       return (char *)(title);
141
142     /* If we haven't yet found any title attribute, check for any
143      * further description elements at the current XML nesting level.
144      */
145     desc = desc->FindNextAssociate( description_key );
146   }
147
148   /* If we've exhausted all description elements at the current XML
149    * nesting level, without finding a title attribute, and we haven't
150    * checked all enclosing levels back to the document root...
151    */
152   if( pkg != xml_root )
153     /*
154      * ...then continue the search in the immediately enclosing level.
155      */
156     return pkgGetTitle( pkg->GetParent(), xml_root );
157
158   /* If we get to here, then we've searched all levels back to the
159    * document root, and have failed to find any title attribute; we
160    * have nothing to return.
161    */
162   return NULL;
163 }
164
165 static inline char *pkgGetTitle( pkgXmlNode *pkg )
166 {
167   /* Overload the preceding function, to automatically generate
168    * the required reference to the XML document root.
169    */
170   return pkgGetTitle( pkg->GetParent(), pkg->GetDocumentRoot() );
171 }
172
173 static inline
174 char *version_string_copy( char *buf, const char *text, int fill = 0 )
175 {
176   /* Local helper function to construct a package version string
177    * from individual version specific elements of the tarname.
178    */
179   if( text != NULL )
180   {
181     /* First, if a fill character is specified, copy it as the
182      * first character of the result; (we assume that we are
183      * appending to a previously constructed result, and that
184      * this is the field separator character).
185      */
186     if( fill != 0 ) *buf++ = fill;
187
188     /* Now, append "text" up to, and including its final NUL
189      * terminator; (note that we do NOT guard against buffer
190      * overrun, as we have complete control over the calling
191      * context, where we allocated a result buffer at least
192      * as long as the tarname string from which the composed
193      * version string is extracted, and the composed result
194      * can never exceed the original length of this).
195      */
196     do { if( (*buf = *text) != '\0' ) ++buf; } while( *text++ != '\0' );
197   }
198   /* Finally, we return a pointer to the terminating NUL of
199    * the result, so as to facilitate appending further text.
200    */
201   return buf;
202 }
203
204 static char *pkgVersionString( char *buf, pkgSpecs *pkg )
205 {
206   /* Helper method to construct a fully qualified version string
207    * from the decomposed package tarname form in a pkgSpecs structure.
208    *
209    * We begin with the concatenation of package version and build ID
210    * fields, retrieved from the pkgSpecs representation...
211    */
212   char *update = version_string_copy( buf, pkg->GetPackageVersion() );
213   update = version_string_copy( update, pkg->GetPackageBuild(), '-' );
214   if( pkg->GetSubSystemVersion() != NULL )
215   {
216     /* ...then, we append the sub-system ID, if applicable...
217      */
218     update = version_string_copy( update, pkg->GetSubSystemName(), '-' );
219     update = version_string_copy( update, pkg->GetSubSystemVersion(), '-' );
220     update = version_string_copy( update, pkg->GetSubSystemBuild(), '-' );
221   }
222   /* ...and finally, we return a pointer to the buffer in which
223    * we constructed the fully qualified version string.
224    */
225   return buf;
226 }
227
228 pkgListViewMaker::pkgListViewMaker( HWND pane ): ListView( pane )
229 {
230   /* Constructor: initialise the invariant parameters within the
231    * embedded W32API ListView control structure.
232    */
233   content.stateMask = 0;
234   content.mask = LVIF_PARAM | LVIF_IMAGE | LVIF_STATE;
235   content.iSubItem = 0;
236   content.iItem = -1;
237 }
238
239 EXTERN_C pkgXmlNode *pkgGetStatus( pkgXmlNode *pkg, pkgActionItem *avail )
240 {
241   /* Helper function to acquire release availability and installation
242    * status attributes for a specified package.
243    */
244   pkg = pkg->FindFirstAssociate( release_key );
245   while( pkg != NULL )
246   {
247     /* Examine each available release specification for the nominated
248      * package; select the one which represents the latest (most recent)
249      * available release.
250      */
251     avail->SelectIfMostRecentFit( pkg );
252
253     /* Also check for the presence of an installation record for each
254      * release; if found, mark it as the currently installed release;
255      * (we assign the "to-remove" attribute, but we don't action it).
256      */
257     if( pkg->GetInstallationRecord( pkg->GetPropVal( tarname_key, NULL )) != NULL )
258       avail->SelectPackage( pkg, to_remove );
259
260     /* Cycle, until all known releases have been examined.
261      */
262     pkg = pkg->FindNextAssociate( release_key );
263   }
264   /* Check the identification of any currently installed release; this
265    * will capture property data for any installed release which may have
266    * been subsequently withdrawn from distribution.
267    */
268   avail->ConfirmInstallationStatus();
269
270   /* Finally, return a pointer to the specification for the installed
271    * release, if any, of the package under consideration.
272    */
273   return avail->Selection( to_remove );
274 }
275
276 void pkgListViewMaker::InsertItem( pkgXmlNode *pkg, char *class_name )
277 {
278   /* Private method to add a single package record, as an individual
279    * row item, to the displayed list view table; begin by filling in
280    * the appropriate fields within the "content" structure...
281    */
282   content.iItem++;
283   content.state = 0;
284   content.lParam = (unsigned long)(pkg);
285
286   /* ...then delegate the actual entry construction to...
287    */
288   UpdateItem( class_name, true );
289 }
290
291 inline bool pkgListViewMaker::GetItem( void )
292 {
293   /* An inline helper method to load the content structure from the
294    * next available item, if any, in the package list view; returns
295    * true when content is successfully loaded, else returns false.
296    */
297   content.iItem++; return ListView_GetItem( ListView, &content );
298 }
299
300 void pkgListViewMaker::UpdateItem( char *class_name, bool new_entry )
301 {
302   /* Assign a temporary action item, through which we may identify
303    * the latest available, and currently installed (if any), version
304    * attributes for the package under consideration.
305    */
306   pkgActionItem avail;
307   pkgXmlNode *package = (pkgXmlNode *)(content.lParam);
308   pkgXmlNode *installed = pkgGetStatus( package, &avail );
309
310   /* Decompose the package tarname identifier for the latest available
311    * release, into its individual package specification properties.
312    */
313   pkgSpecs latest( package = avail.Selection() );
314
315   /* Allocate a temporary working text buffer, in which to format
316    * package property values for display...
317    */
318   size_t len = strlen( package->GetPropVal( tarname_key, value_none ) );
319   if( installed != NULL )
320   {
321     /* ...ensuring that it is at least as large as the longer of the
322      * latest or installed release tarname.
323      */
324     size_t altlen = strlen( installed->GetPropVal( tarname_key, value_none ) );
325     if( altlen > len ) len = altlen;
326   }
327   char buf[1 + len];
328
329   /* Choose a suitable icon for representation of the installation
330    * status of the package under consideration...
331    */
332   if( installed != NULL )
333   {
334     /* ...noting that, when it is already installed...
335      */
336     pkgSpecs current( installed );
337     content.iImage = (latest > current)
338       ? /* ...and, when the latest available is NEWER than the
339          * installed version, then we choose the icon indicating
340          * an installed package with an available update...
341          */
342         PKGSTATE( INSTALLED_OLD )
343
344       : /* ...or, when the latest available is NOT NEWER than
345          * the installed version, then we choose the alternative
346          * icon, simply indicating an installed package...
347          */
348         PKGSTATE( INSTALLED_CURRENT );
349
350     /* ...and also, load the version identification string for
351      * the installed version into the working text buffer.
352      */
353     pkgVersionString( buf, &current );
354   }
355   else
356   { /* Alternatively, for any package which is not recorded as
357      * installed, choose the icon indicating an available, but
358      * not (yet) installed package...
359      */
360     content.iImage = PKGSTATE( AVAILABLE );
361
362     /* ...and mark the list view column entry, for the installed
363      * version, as empty.
364      */
365     *buf = '\0';
366   }
367
368   /* When compiling a new list entry...
369    */
370   if( new_entry )
371   {
372     /* ...add the package identification record, as a new item...
373      */
374     ListView_InsertItem( ListView, &content );
375     /*
376      * ...and fill in the text for the package name, class name,
377      * and package description columns...
378      */
379     ListView_SetItemText( ListView, content.iItem, 1, package_name );
380     ListView_SetItemText( ListView, content.iItem, 2, class_name );
381     ListView_SetItemText( ListView, content.iItem, 5, pkgGetTitle( package ) );
382   }
383   else
384     /* ...otherwise, this is simply a request to update an
385      * existing item, in place; do so.  (Note that, in this
386      * case, we DO NOT touch the package name, class name,
387      * or package description column content).
388      */
389     ListView_SetItem( ListView, &content );
390
391   /* Always fill in the text, as established above, in the
392    * column which identifies the currently installed version,
393    * if any, or clear it if the package is not installed.
394    */
395   ListView_SetItemText( ListView, content.iItem, 3, buf );
396
397   /* Finally, fill in the text of the column which identifies
398    * the latest available version of the package.
399    */
400   ListView_SetItemText( ListView, content.iItem, 4, pkgVersionString( buf, &latest ) );
401 }
402
403 void pkgListViewMaker::Dispatch( pkgXmlNode *package )
404 {
405   /* Implementation of the "dispatcher" method, which is required
406    * by any derivative of the pkgDirectoryViewerEngine class, for
407    * dispatching the content of the directory to the display service,
408    * (which, in this case, populates the list view window pane).
409    */
410   if( package->IsElementOfType( package_key ) )
411   {
412     /* Assemble the package name into the list view record block.
413      */
414     package_name = (char *)(package->GetPropVal( name_key, value_unknown ));
415
416     /* When processing a pkgDirectory entry for a package entity,
417      * generate a sub-directory listing for any contained package
418      * components...
419      */
420     pkgDirectory *dir;
421     if( (dir = EnumerateComponents( package )) != NULL )
422     {
423       /* ...and recurse, to process any which are found...
424        */
425       dir->InOrder( this );
426       delete dir;
427     }
428     else
429       /* ...otherwise, simply insert an unclassified list entry
430        * for the bare package name, omitting the component class.
431        */
432       InsertItem( package, "" );
433   }
434   else if( package->IsElementOfType( component_key ) )
435   {
436     /* Handle the recursive calls for the component sub-directory,
437      * inheriting the package name entry from the original package
438      * entity, and emit an appropriately classified list view entry
439      * for each identified component package.
440      */
441     InsertItem( package, (char *)(package->GetPropVal( class_key, "" )) );
442   }
443 }
444
445 void pkgListViewMaker::MarkScheduledActions( pkgActionItem *schedule )
446 {
447   /* Method to reassign icons to entries within the package list view,
448    * indicating any which have been marked for installation, upgrade,
449    * or removal of the associated package.
450    */
451   if( schedule != NULL )
452     for( content.iItem = -1; GetItem(); )
453     {
454       /* Visiting every entry in the list...
455        */
456       pkgActionItem *ref;
457       if( (ref = schedule->GetReference( (pkgXmlNode *)(content.lParam) )) != NULL )
458       {
459         /* ...identify those which are associated with a scheduled action...
460          */
461         unsigned long opcode;
462         if( (opcode = ref->HasAttribute( ACTION_MASK )) == ACTION_INSTALL )
463         {
464           /* ...selecting the appropriate icon to mark those packages
465            * which have been scheduled for installation...
466            *
467            * FIXME: we should also consider that such packages
468            * may have been scheduled for reinstallation.
469            */
470           content.iImage = PKGSTATE( AVAILABLE_INSTALL );
471         }
472         else if( opcode == ACTION_UPGRADE )
473         {
474           /* ...those which have been scheduled for upgrade...
475            */
476           content.iImage = PKGSTATE( UPGRADE );
477         }
478         else if( opcode == ACTION_REMOVE )
479         {
480           /* ...and those which have been scheduled for removal.
481            */
482           content.iImage = PKGSTATE( REMOVE );
483         }
484         else
485         { /* Where a scheduled action is any other than those above,
486            * handle as if there was no scheduled action...
487            */
488           opcode = 0UL;
489           /*
490            * ...and ensure that the list view entry reflects the
491            * normal display state for the associated package.
492            */
493           UpdateItem( NULL );
494         }
495         if( opcode != 0UL )
496           /*
497            * Where an action mark is appropriate, ensure that it
498            * is applied to the list view entry.
499            */
500           ListView_SetItem( ListView, &content );
501       }
502     }
503 }
504
505 void pkgListViewMaker::UpdateListView( void )
506 {
507   /* A simple helper method, to refresh the content of the
508    * package list view, resetting each item to its initial
509    * unmarked display state.
510    */
511   for( content.iItem = -1; GetItem(); ) UpdateItem( NULL );
512 }
513
514 /* $RCSfile$: end of file */