2 * title: PackageInspector.m
3 * abstract: NEXTSTEP Workspace Manager Inspector for Installer ".pkg" files.
4 * author: T.R.Hageman, Groningen, The Netherlands
5 * created: November 1994
6 * modified: (see RCS Log at end)
9 * Copyright (C) 1994,1995 Tom R. Hageman.
11 * This is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This software is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this software; if not, write to the Free Software
23 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
30 static const char RCSid[] =
31 "PackageInspector.m,v 1.8 1995/09/01 21:46:27";
34 #define VERSION "0.951"
39 #define LISTCONTENTS 0 // List Contents not yet implemented
41 #import "PackageInspector.h"
47 #define OPENBUTTON NXLocalizedStringFromTableInBundle(NULL, bundle, "Open", NULL, button label)
48 #define LISTCONTENTSBUTTON NXLocalizedStringFromTableInBundle(NULL, bundle, "List Contents", NULL, button label)
49 #define LISTDESCRIPTIONBUTTON NXLocalizedStringFromTableInBundle(NULL, bundle, "Description", NULL, button label)
52 #define STATE_UNINSTALLED NXLocalizedStringFromTableInBundle(NULL, bundle, "Uninstalled", NULL, original package state)
53 #define STATE_INSTALLED NXLocalizedStringFromTableInBundle(NULL, bundle, "installed", "Installed", package has been uncompressed unto disk)
54 #define STATE_COMPRESSD NXLocalizedStringFromTableInBundle(NULL, bundle, "compressed", "Compressed", installed package has been recompressed)
56 // so InfoView.strings can be ripped off from Installer.app
57 #define SIZEFORMAT NXLocalizedStringFromTableInBundle("InfoView", bundle, "%s installed, %s compressed", NULL, Short indication to user about the size of a package once installed and the size when compressed)
58 #define KBYTES NXLocalizedStringFromTableInBundle("InfoView", bundle, "KB", NULL, Kilobytes -- package size)
59 #define MBYTES NXLocalizedStringFromTableInBundle("InfoView", bundle, "MB", NULL, MegaBytes -- package size)
61 #define LOCALIZE(s) NXLoadLocalizedStringFromTableInBundle(NULL, bundle, s, NULL)
62 #define LOCALIZE_ARCH(s) NXLoadLocalizedStringFromTableInBundle("Architectures", bundle, s, NULL)
65 @implementation PackageInspector
69 static PackageInspector *instance;
71 if (instance == nil) {
72 char path[MAXPATHLEN+1];
73 const char *nibname = [self name];
75 instance = [super new];
77 instance->bundle = [NXBundle bundleForClass:self];
79 if ([instance->bundle getPath:path forResource:nibname ofType:"nib"] &&
80 [NXApp loadNibFile:path owner:instance]) {
81 [instance->inspectorVersionField setStringValue:VERSION];
82 [instance->packageDescriptionText setVertResizable:YES]; // ??Necessary??
85 fprintf(stderr, "Couldn't load %s.nib\n", nibname);
95 if (infoPanel == nil) {
96 char path[MAXPATHLEN+1];
98 if ([bundle getPath:path forResource:"Info" ofType:"nib"] &&
99 [NXApp loadNibFile:path owner:self]) {
100 [infoVersionField setStringValue:[inspectorVersionField stringValue]];
103 [infoPanel makeKeyAndOrderFront:sender];
109 [super revert:sender];
111 if ([self selectionCount] != 1) {
114 if (sender == [self revertButton]) {
115 [self toggleDescription];
118 char path[MAXPATHLEN+1];
121 [self selectionPathsInto:path separator:'\0'];
122 if (!(package = [[NXBundle allocFromZone:[self zone]] initForDirectory:path])) {
125 if ([self shouldLoad]) {
127 revertButtonState = listContents;
130 [[[self okButton] setTitle:OPENBUTTON] setEnabled:YES];
131 [self setRevertButtonTitle];
138 [self perform:@selector(open:) with:sender afterDelay:0 cancelPrevious:NO];
145 char buf[256], size[2][20];
146 HashTable *table = [[HashTable alloc] initKeyDesc:"*" valueDesc:"*"];
149 // Collect information about the package in a hashtable.
150 [self loadKeyValuesFrom:"info" inTable:table];
151 [self loadKeyValuesFrom:"sizes" inTable:table];
152 [self loadContentsOf:"location" inTable:table];
153 [self loadContentsOf:"status" inTable:table];
155 // Convenience macro.
156 #define LOOKUP(key, notfound) ([table isKey:key] ? [table valueForKey:key] : \
159 // Set the various controls.
160 sprintf(buf, "<<not yet implemented>>");
161 // Well then, how *DOES* Installer determine this???
162 [packageArchesField setStringValue:buf];
164 [packageDescriptionText setText:LOOKUP("Description", "")];
165 [packageLocationField setStringValue:
166 LOOKUP("location", LOOKUP("DefaultLocation", "???"))];
168 [self formatSize:[table valueForKey:"InstalledSize"] inBuf:size[0]];
169 [self formatSize:[table valueForKey:"CompressedSize"] inBuf:size[1]];
170 sprintf(buf, SIZEFORMAT, size[0], size[1]);
171 [packageSizesField setStringValue:buf];
173 [packageStatusField setStringValue:LOCALIZE(LOOKUP("status", "Uninstalled"))];
174 [packageTitleField setStringValue:LOOKUP("Title", "???")];
175 [packageVersionField setStringValue:LOOKUP("Version", "???")];
177 // Is this how one frees the contents of a hashtable?
178 [table freeKeys:free values:free];
186 -loadKeyValuesFrom:(const char *)type inTable:(HashTable *)table
188 char path[MAXPATHLEN+1];
191 if (stream = NXMapFile([self getPath:path forType:type], NX_READONLY)) {
195 fprintf(stderr, "loadKeyValuesFrom:%s\n", path);
197 while ((c = NXGetc(stream)) >= 0) {
198 // Buffer sizes should be enough, according to doc.
199 char key[1024+1], value[1024+1];
202 if (NXIsSpace(c)) continue;
204 while ((c = NXGetc(stream)) >= 0 && c != '\n') ;
207 // Found key; collect it.
210 if (p < &key[sizeof key-1]) *p++ = c;
211 } while ((c = NXGetc(stream)) >= 0 && !NXIsSpace(c));
214 // Skip over spaces and tabs.
215 while (c == ' ' || c == '\t') c = NXGetc(stream);
217 // Value is rest of line, up to newline.
220 if (p < &value[sizeof value-1]) *p++ = c;
221 } while ((c = NXGetc(stream)) >= 0 && c != '\n');
224 // Insert key/value pair in hashtable.
226 fprintf(stderr, "key:%s value:%s\n", key, value);
228 [table insertKey:NXCopyStringBuffer(key)
229 value:NXCopyStringBuffer(value)];
232 NXCloseMemory(stream, NX_FREEBUFFER);
238 -loadContentsOf:(const char *)type inTable:(HashTable *)table
240 char path[MAXPATHLEN+1];
243 if (stream = NXMapFile([self getPath:path forType:type], NX_READONLY)) {
245 int n = NXRead(stream, line, sizeof line);
247 if (n > 0 && line[n-1] == '\n') line[n-1] = '\0'; // remove trailing newline.
249 NXCloseMemory(stream, NX_FREEBUFFER);
251 [table insertKey:NXCopyStringBuffer(type)
252 value:NXCopyStringBuffer(line)];
259 char path[MAXPATHLEN+1];
262 // Remove old image from the button.
263 if (image = [packageIconButton image]) {
264 [packageIconButton setImage:nil];
267 // Get the image (if any) from the package
268 image = [[NXImage allocFromZone:[self zone]] initFromFile:[self getPath:path forType:"tiff"]];
269 [packageIconButton setImage:image];
275 #define STAT_EQ(s1, s2) ((s1)->st_ino == (s2)->st_ino && \
276 (s1)->st_dev == (s2)->st_dev && \
277 (s1)->st_mtime == (s2)->st_mtime && \
278 (s1)->st_size == (s2)->st_size)
282 char path[MAXPATHLEN+1];
283 struct stat newstats[NUMSTATS];
284 static const char * const typesToStat[NUMSTATS] = { TYPESTOSTAT };
288 for (i = 0; i < NUMSTATS; i++) {
289 memset(&newstats[i], 0, sizeof(struct stat));
290 if (!(stat([self getPath:path forType:typesToStat[i]], &newstats[i]) == 0 &&
291 STAT_EQ(&newstats[i], &stats[i]))) {
293 ///break; // NOT!!! must stat all for accurate cache.
295 stats[i] = newstats[i];
303 switch (revertButtonState) {
306 revertButtonState = listDescription;
308 case listDescription:
309 revertButtonState = listContents;
312 return [self setRevertButtonTitle];
317 -(const char *)getPath:(char *)buf forType:(const char *)type
319 char name[MAXPATHLEN+1];
321 // Get package name, sans extension.
322 *strrchr(strcpy(name, strrchr([package directory], '/')+1), '.') = '\0';
324 // Now get the full pathname.
325 [package getPath:buf forResource:name ofType:type];
327 fprintf(stderr, "PackageInspector: type=\"%s\" name=\"%s\" path=\"%s\"\n",
333 -setRevertButtonTitle
336 [[[self revertButton]
337 setTitle:LOCALIZE(revertButtonState == listContents ?
338 "List Contents" : "Description")]
344 -(const char *)formatSize:(const char *)size inBuf:(char *)buf
346 // [TRH] this is very simplistic (but seems consistent with Installer.app)
351 int len = strlen(size);
353 sprintf(buf, "%s%s", size, KBYTES);
356 sprintf(buf, "%.*s.%.*s%s",
357 (len-3), size, 3-(len-3), size+(len-3), MBYTES);
360 sprintf(buf, "%.*s%s", (len-3), size, MBYTES);
366 // Determine architectures, in separate subprocess.
368 #define WORKING " ..." // `I'm still busy' indicator.
372 char command[2*MAXPATHLEN+10+1];
374 if (archProcess) [archProcess terminate:self];
376 [packageArchesField setStringValue:WORKING];
378 [bundle getPath:command forResource:"archbom" ofType:NULL];
379 strcat(command, " ");
380 [self getPath:&command[strlen(command)] forType:"bom"];
381 archProcess = [[Subprocess allocFromZone:[self zone]] init:command
382 withDelegate:self andPtySupport:NO andStdErr:NO];
385 -(void)addArchs:(const char *)string
387 char result[1024]; // Should be big enough...
391 strcpy(result, [packageArchesField stringValue]);
392 if ((d = strstr(result, WORKING)) != NULL) {
396 d = result + strlen(result);
403 while (*s && !NXIsAlNum(*s)) {
411 while (NXIsAlNum(*s)) *t++ = *s++;
415 fprintf(stderr, "addArchs:\"%s\" localized: \"%s\"\n", name, LOCALIZE_ARCH(name));
417 strcpy(d, LOCALIZE_ARCH(name));
424 [packageArchesField setStringValue:result];
425 [window displayIfNeeded]; // necessary??
428 -subprocess:(Subprocess *)sender output:(char *)buffer
430 if (sender == archProcess) {
431 [self addArchs:buffer];
436 -subprocessDone:(Subprocess *)sender
438 if (sender == archProcess) {
440 [self addArchs:NULL];
446 static void openInWorkspace(const char *filename)
448 // Indirect approach to circumvent Workspace deadlock/timeout.
449 char command[14+3*MAXPATHLEN+1];
453 for (s = "exec open '"; *s; ) *d++ = *s++;
454 // Escape single quote characters.
455 for (s = filename; *s; ) {
456 if ((*d++ = *s++) == '\'') {
457 *d++ = '\\', *d++ = '\'', *d++ = '\'';
460 for (s = "'&"; *d++ = *s++; ) ;
466 openInWorkspace([package directory]);
472 /*======================================================================
473 * PackageInspector.m,v
474 * Revision 1.8 1995/09/01 21:46:27 tom
475 * Circumvent open deadlock/timeout (when Installer.app is not yet launched);
476 * (openInWorkspace): new private function; (-open:): new method.
478 * Revision 1.7 1995/07/30 22:20:26 tom
479 * (LOCALIZE_ARCH): new macro; (-addArchs:): new method;
480 * (-subprocess:output:,-subprocessDone:) use it.
482 * Revision 1.6 1995/07/30 16:59:51 tom
483 * import Subprocess.h; (archProcess): new ivar;
484 * (-getArchs,-subprocess:output:,-subprocessDone:): new methods;
485 * added for asynchronous arch-determination.
487 * Revision 1.5 1995/07/29 19:13:35 tom
488 * (+new): avoid reassignment of self;
489 * make packageDescriptionText vertically resizable;
490 * (-shouldLoad): rewritten to generalized array-driven approach.
492 * Revision 1.4 1995/04/02 02:39:01 tom
493 * (package): NXBundle instead of (const char *). so that localized info files
494 * are found. (this loses out if *.pkg is a symbolic link, though.)
496 * Revision 1.3 1994/12/07 00:00:36 tom
497 * (RCSid): add spaces.
499 * Revision 1.2 1994/11/25 21:27:18 tom
500 * (package ivar): use (char*) instead of (NXBundle*) to workaround symlink problems
502 * Revision 1.1 1994/11/25 16:13:12 tom
505 *======================================================================*/