]> code.delx.au - gnu-emacs/blob - test/manual/etags/objc-src/PackInsp.m
-
[gnu-emacs] / test / manual / etags / objc-src / PackInsp.m
1 /*+++*
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)
7 * copyleft:
8 *
9 * Copyright (C) 1994,1995 Tom R. Hageman.
10 *
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.
15 *
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.
20 *
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.
24 *
25 * description:
26 *
27 *---*/
28
29 #ifdef RCS_ID
30 static const char RCSid[] =
31 "PackageInspector.m,v 1.8 1995/09/01 21:46:27";
32 #endif
33
34 #define VERSION "0.951"
35
36 #ifndef DEBUG
37 # define DEBUG 0
38 #endif
39 #define LISTCONTENTS 0 // List Contents not yet implemented
40
41 #import "PackageInspector.h"
42 #include <string.h>
43 #include <stdlib.h>
44 #include <stdio.h>
45
46 // Localized strings
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)
50
51 // States
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)
55
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)
60
61 #define LOCALIZE(s) NXLoadLocalizedStringFromTableInBundle(NULL, bundle, s, NULL)
62 #define LOCALIZE_ARCH(s) NXLoadLocalizedStringFromTableInBundle("Architectures", bundle, s, NULL)
63
64
65 @implementation PackageInspector
66
67 +new
68 {
69 static PackageInspector *instance;
70
71 if (instance == nil) {
72 char path[MAXPATHLEN+1];
73 const char *nibname = [self name];
74
75 instance = [super new];
76
77 instance->bundle = [NXBundle bundleForClass:self];
78
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??
83 }
84 else {
85 fprintf(stderr, "Couldn't load %s.nib\n", nibname);
86 [instance free];
87 instance = nil;
88 }
89 }
90 return instance;
91 }
92
93 -showInfo:sender
94 {
95 if (infoPanel == nil) {
96 char path[MAXPATHLEN+1];
97
98 if ([bundle getPath:path forResource:"Info" ofType:"nib"] &&
99 [NXApp loadNibFile:path owner:self]) {
100 [infoVersionField setStringValue:[inspectorVersionField stringValue]];
101 }
102 }
103 [infoPanel makeKeyAndOrderFront:sender];
104 return self;
105 }
106
107 -revert:sender
108 {
109 [super revert:sender];
110
111 if ([self selectionCount] != 1) {
112 return nil;
113 }
114 if (sender == [self revertButton]) {
115 [self toggleDescription];
116 }
117 else {
118 char path[MAXPATHLEN+1];
119
120 [package free];
121 [self selectionPathsInto:path separator:'\0'];
122 if (!(package = [[NXBundle allocFromZone:[self zone]] initForDirectory:path])) {
123 return nil;
124 }
125 if ([self shouldLoad]) {
126 [self load];
127 revertButtonState = listContents;
128 }
129 }
130 [[[self okButton] setTitle:OPENBUTTON] setEnabled:YES];
131 [self setRevertButtonTitle];
132
133 return self;
134 }
135
136 -ok:sender
137 {
138 [self perform:@selector(open:) with:sender afterDelay:0 cancelPrevious:NO];
139 [super ok:sender];
140 return self;
141 }
142
143 -load
144 {
145 char buf[256], size[2][20];
146 HashTable *table = [[HashTable alloc] initKeyDesc:"*" valueDesc:"*"];
147
148 [self getArchs];
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];
154
155 // Convenience macro.
156 #define LOOKUP(key, notfound) ([table isKey:key] ? [table valueForKey:key] : \
157 (notfound))
158 #if 0
159 // Set the various controls.
160 sprintf(buf, "<<not yet implemented>>");
161 // Well then, how *DOES* Installer determine this???
162 [packageArchesField setStringValue:buf];
163 #endif
164 [packageDescriptionText setText:LOOKUP("Description", "")];
165 [packageLocationField setStringValue:
166 LOOKUP("location", LOOKUP("DefaultLocation", "???"))];
167
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];
172
173 [packageStatusField setStringValue:LOCALIZE(LOOKUP("status", "Uninstalled"))];
174 [packageTitleField setStringValue:LOOKUP("Title", "???")];
175 [packageVersionField setStringValue:LOOKUP("Version", "???")];
176 #undef LOOKUP
177 // Is this how one frees the contents of a hashtable?
178 [table freeKeys:free values:free];
179 [table free];
180
181 [self loadImage];
182
183 return self;
184 }
185
186 -loadKeyValuesFrom:(const char *)type inTable:(HashTable *)table
187 {
188 char path[MAXPATHLEN+1];
189 NXStream *stream;
190
191 if (stream = NXMapFile([self getPath:path forType:type], NX_READONLY)) {
192 int c;
193
194 #if DEBUG & 1
195 fprintf(stderr, "loadKeyValuesFrom:%s\n", path);
196 #endif
197 while ((c = NXGetc(stream)) >= 0) {
198 // Buffer sizes should be enough, according to doc.
199 char key[1024+1], value[1024+1];
200 char *p;
201
202 if (NXIsSpace(c)) continue;
203 if (c == '#') {
204 while ((c = NXGetc(stream)) >= 0 && c != '\n') ;
205 continue;
206 }
207 // Found key; collect it.
208 p = key;
209 do {
210 if (p < &key[sizeof key-1]) *p++ = c;
211 } while ((c = NXGetc(stream)) >= 0 && !NXIsSpace(c));
212 *p = '\0';
213
214 // Skip over spaces and tabs.
215 while (c == ' ' || c == '\t') c = NXGetc(stream);
216
217 // Value is rest of line, up to newline.
218 p = value;
219 do {
220 if (p < &value[sizeof value-1]) *p++ = c;
221 } while ((c = NXGetc(stream)) >= 0 && c != '\n');
222 *p = '\0';
223
224 // Insert key/value pair in hashtable.
225 #if DEBUG & 1
226 fprintf(stderr, "key:%s value:%s\n", key, value);
227 #endif
228 [table insertKey:NXCopyStringBuffer(key)
229 value:NXCopyStringBuffer(value)];
230 }
231
232 NXCloseMemory(stream, NX_FREEBUFFER);
233 }
234 return self;
235
236 }
237
238 -loadContentsOf:(const char *)type inTable:(HashTable *)table
239 {
240 char path[MAXPATHLEN+1];
241 NXStream *stream;
242
243 if (stream = NXMapFile([self getPath:path forType:type], NX_READONLY)) {
244 char line[1024+1];
245 int n = NXRead(stream, line, sizeof line);
246
247 if (n > 0 && line[n-1] == '\n') line[n-1] = '\0'; // remove trailing newline.
248
249 NXCloseMemory(stream, NX_FREEBUFFER);
250
251 [table insertKey:NXCopyStringBuffer(type)
252 value:NXCopyStringBuffer(line)];
253 }
254 return self;
255 }
256
257 -loadImage
258 {
259 char path[MAXPATHLEN+1];
260 NXImage *image;
261
262 // Remove old image from the button.
263 if (image = [packageIconButton image]) {
264 [packageIconButton setImage:nil];
265 [image free];
266 }
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];
270
271 return self;
272 }
273
274
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)
279
280 -(BOOL)shouldLoad
281 {
282 char path[MAXPATHLEN+1];
283 struct stat newstats[NUMSTATS];
284 static const char * const typesToStat[NUMSTATS] = { TYPESTOSTAT };
285 BOOL result = NO;
286 int i;
287
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]))) {
292 result = YES;
293 ///break; // NOT!!! must stat all for accurate cache.
294 }
295 stats[i] = newstats[i];
296 }
297
298 return result;
299 }
300
301 -toggleDescription
302 {
303 switch (revertButtonState) {
304 case listContents:
305 // TODO: swap views?
306 revertButtonState = listDescription;
307 break;
308 case listDescription:
309 revertButtonState = listContents;
310 break;
311 }
312 return [self setRevertButtonTitle];
313 }
314
315
316 // Support methods
317 -(const char *)getPath:(char *)buf forType:(const char *)type
318 {
319 char name[MAXPATHLEN+1];
320
321 // Get package name, sans extension.
322 *strrchr(strcpy(name, strrchr([package directory], '/')+1), '.') = '\0';
323
324 // Now get the full pathname.
325 [package getPath:buf forResource:name ofType:type];
326 #if DEBUG & 2
327 fprintf(stderr, "PackageInspector: type=\"%s\" name=\"%s\" path=\"%s\"\n",
328 type, name, buf);
329 #endif
330 return buf;
331 }
332
333 -setRevertButtonTitle
334 {
335 #if LISTCONTENTS
336 [[[self revertButton]
337 setTitle:LOCALIZE(revertButtonState == listContents ?
338 "List Contents" : "Description")]
339 setEnabled:YES];
340 #endif
341 return self;
342 }
343
344 -(const char *)formatSize:(const char *)size inBuf:(char *)buf
345 {
346 // [TRH] this is very simplistic (but seems consistent with Installer.app)
347 if (!size) {
348 strcpy(buf, "???");
349 }
350 else {
351 int len = strlen(size);
352 if (len < 4) {
353 sprintf(buf, "%s%s", size, KBYTES);
354 }
355 else if (len < 6) {
356 sprintf(buf, "%.*s.%.*s%s",
357 (len-3), size, 3-(len-3), size+(len-3), MBYTES);
358 }
359 else {
360 sprintf(buf, "%.*s%s", (len-3), size, MBYTES);
361 }
362 }
363 return buf;
364 }
365
366 // Determine architectures, in separate subprocess.
367
368 #define WORKING " ..." // `I'm still busy' indicator.
369
370 -(void)getArchs
371 {
372 char command[2*MAXPATHLEN+10+1];
373
374 if (archProcess) [archProcess terminate:self];
375
376 [packageArchesField setStringValue:WORKING];
377
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];
383 }
384
385 -(void)addArchs:(const char *)string
386 {
387 char result[1024]; // Should be big enough...
388 const char *s;
389 char *d;
390
391 strcpy(result, [packageArchesField stringValue]);
392 if ((d = strstr(result, WORKING)) != NULL) {
393 *d = '\0';
394 }
395 else {
396 d = result + strlen(result);
397 }
398 if ((s = string)) {
399 do {
400 char name[100];
401 char *t = name;
402
403 while (*s && !NXIsAlNum(*s)) {
404 if (*s == '\n') {
405 *d++ = ' ', s++;
406 }
407 else {
408 *d++ = *s++;
409 }
410 }
411 while (NXIsAlNum(*s)) *t++ = *s++;
412 *t = '\0';
413 if (t > name) {
414 #if DEBUG & 4
415 fprintf(stderr, "addArchs:\"%s\" localized: \"%s\"\n", name, LOCALIZE_ARCH(name));
416 #endif
417 strcpy(d, LOCALIZE_ARCH(name));
418 d += strlen(d);
419 }
420 } while (*s);
421
422 strcpy(d, WORKING);
423 }
424 [packageArchesField setStringValue:result];
425 [window displayIfNeeded]; // necessary??
426 }
427
428 -subprocess:(Subprocess *)sender output:(char *)buffer
429 {
430 if (sender == archProcess) {
431 [self addArchs:buffer];
432 }
433 return self;
434 }
435
436 -subprocessDone:(Subprocess *)sender
437 {
438 if (sender == archProcess) {
439 archProcess = nil;
440 [self addArchs:NULL];
441 }
442 [sender free];
443 return self;
444 }
445
446 static void openInWorkspace(const char *filename)
447 {
448 // Indirect approach to circumvent Workspace deadlock/timeout.
449 char command[14+3*MAXPATHLEN+1];
450 const char *s;
451 char *d = command;
452
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++ = '\'';
458 }
459 }
460 for (s = "'&"; *d++ = *s++; ) ;
461 system(command);
462 }
463
464 -open:sender
465 {
466 openInWorkspace([package directory]);
467 return self;
468 }
469
470 @end
471
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.
477 *
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.
481 *
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.
486 *
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.
491 *
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.)
495 *
496 * Revision 1.3 1994/12/07 00:00:36 tom
497 * (RCSid): add spaces.
498 *
499 * Revision 1.2 1994/11/25 21:27:18 tom
500 * (package ivar): use (char*) instead of (NXBundle*) to workaround symlink problems
501 *
502 * Revision 1.1 1994/11/25 16:13:12 tom
503 * Initial revision
504 *
505 *======================================================================*/