|
ingmarstein@1791
|
1 |
//
|
|
boredzo@2467
|
2 |
// GrowlPathUtil.m
|
|
ingmarstein@1791
|
3 |
// Growl
|
|
ingmarstein@1791
|
4 |
//
|
|
ingmarstein@1791
|
5 |
// Created by Ingmar Stein on 17.04.05.
|
|
ingmarstein@3040
|
6 |
// Copyright 2005-2006 The Growl Project. All rights reserved.
|
|
ingmarstein@1791
|
7 |
//
|
|
ingmarstein@1791
|
8 |
// This file is under the BSD License, refer to License.txt for details
|
|
ingmarstein@1791
|
9 |
|
|
ingmarstein@2470
|
10 |
#import "GrowlPathUtilities.h"
|
|
ingmarstein@2472
|
11 |
#import "GrowlPreferencesController.h"
|
|
ingmarstein@2472
|
12 |
#import "GrowlTicketController.h"
|
|
boredzo@2479
|
13 |
#import "GrowlDefinesInternal.h"
|
|
ingmarstein@1791
|
14 |
|
|
ingmarstein@1791
|
15 |
static NSBundle *helperAppBundle;
|
|
ingmarstein@1791
|
16 |
static NSBundle *prefPaneBundle;
|
|
ingmarstein@1791
|
17 |
|
|
boredzo@2477
|
18 |
#define NAME_OF_SCREENSHOTS_DIRECTORY @"Screenshots"
|
|
boredzo@2477
|
19 |
#define NAME_OF_TICKETS_DIRECTORY @"Tickets"
|
|
boredzo@2477
|
20 |
#define NAME_OF_PLUGINS_DIRECTORY @"Plugins"
|
|
boredzo@2477
|
21 |
|
|
boredzo@2677
|
22 |
@implementation GrowlPathUtilities
|
|
boredzo@2677
|
23 |
|
|
boredzo@2467
|
24 |
#pragma mark Bundles
|
|
ingmarstein@1791
|
25 |
|
|
boredzo@4043
|
26 |
//Searches the process list (as yielded by GetNextProcess) for a process with the given bundle identifier.
|
|
boredzo@4043
|
27 |
//Returns the oldest matching process.
|
|
boredzo@4043
|
28 |
+ (NSBundle *) bundleForProcessWithBundleIdentifier:(NSString *)identifier
|
|
boredzo@4043
|
29 |
{
|
|
boredzo@4043
|
30 |
|
|
boredzo@4043
|
31 |
restart:;
|
|
boredzo@4043
|
32 |
OSStatus err;
|
|
boredzo@4043
|
33 |
NSBundle *bundle = nil;
|
|
boredzo@4043
|
34 |
struct ProcessSerialNumber psn = { 0, 0 };
|
|
boredzo@4043
|
35 |
UInt32 oldestProcessLaunchDate = UINT_MAX;
|
|
boredzo@4043
|
36 |
|
|
boredzo@4043
|
37 |
while ((err = GetNextProcess(&psn)) == noErr) {
|
|
Rudy@4246
|
38 |
struct ProcessInfoRec info = { .processInfoLength = (UInt32)sizeof(struct ProcessInfoRec) };
|
|
boredzo@4043
|
39 |
err = GetProcessInformation(&psn, &info);
|
|
boredzo@4043
|
40 |
if (err == noErr) {
|
|
boredzo@4043
|
41 |
//Compare the launch dates first, since it's cheaper than comparing bundle IDs.
|
|
boredzo@4043
|
42 |
if (info.processLaunchDate < oldestProcessLaunchDate) {
|
|
boredzo@4043
|
43 |
//This one is older (fewer ticks since startup), so this is our current prospect to be the result.
|
|
boredzo@4043
|
44 |
NSDictionary *dict = (NSDictionary *)ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask);
|
|
Rudy@4263
|
45 |
|
|
boredzo@4043
|
46 |
if (dict) {
|
|
Rudy@4263
|
47 |
CFMakeCollectable(dict);
|
|
boredzo@4043
|
48 |
pid_t pid = 0;
|
|
boredzo@4043
|
49 |
GetProcessPID(&psn, &pid);
|
|
boredzo@4043
|
50 |
if ([[dict objectForKey:(NSString *)kCFBundleIdentifierKey] isEqualToString:identifier]) {
|
|
boredzo@4043
|
51 |
NSString *bundlePath = [dict objectForKey:@"BundlePath"];
|
|
boredzo@4043
|
52 |
if (bundlePath) {
|
|
boredzo@4043
|
53 |
bundle = [NSBundle bundleWithPath:bundlePath];
|
|
boredzo@4043
|
54 |
oldestProcessLaunchDate = info.processLaunchDate;
|
|
boredzo@4043
|
55 |
}
|
|
boredzo@4043
|
56 |
}
|
|
boredzo@4043
|
57 |
|
|
boredzo@4043
|
58 |
[dict release];
|
|
boredzo@4043
|
59 |
} else {
|
|
boredzo@4043
|
60 |
//ProcessInformationCopyDictionary returning NULL probably means that the process disappeared out from under us (i.e., exited) in between GetProcessInformation and ProcessInformationCopyDictionary. Start over.
|
|
boredzo@4043
|
61 |
goto restart;
|
|
boredzo@4043
|
62 |
}
|
|
boredzo@4043
|
63 |
}
|
|
boredzo@4043
|
64 |
} else {
|
|
boredzo@4043
|
65 |
if (err != noErr) {
|
|
boredzo@4043
|
66 |
//Unexpected failure of GetProcessInformation (Process Manager got confused?). Assume severe breakage and bail.
|
|
boredzo@4043
|
67 |
NSLog(@"Couldn't get information about process %lu,%lu: GetProcessInformation returned %i/%s", psn.highLongOfPSN, psn.lowLongOfPSN, err, GetMacOSStatusCommentString(err));
|
|
boredzo@4043
|
68 |
err = noErr; //So our NSLog for GetNextProcess doesn't complain. (I wish I had Python's while..else block.)
|
|
boredzo@4043
|
69 |
break;
|
|
boredzo@4043
|
70 |
} else {
|
|
boredzo@4043
|
71 |
//Process disappeared out from under us (i.e., exited) in between GetNextProcess and GetProcessInformation. Start over.
|
|
boredzo@4043
|
72 |
goto restart;
|
|
boredzo@4043
|
73 |
}
|
|
boredzo@4043
|
74 |
}
|
|
boredzo@4043
|
75 |
}
|
|
boredzo@4043
|
76 |
if (err != procNotFound) {
|
|
boredzo@4043
|
77 |
NSLog(@"%s: GetNextProcess returned %i/%s", __PRETTY_FUNCTION__, err, GetMacOSStatusCommentString(err));
|
|
boredzo@4043
|
78 |
}
|
|
boredzo@4043
|
79 |
|
|
boredzo@4043
|
80 |
return bundle;
|
|
boredzo@4043
|
81 |
}
|
|
boredzo@4043
|
82 |
|
|
boredzo@4043
|
83 |
//Obtains the bundle for the active GrowlHelperApp process. Returns nil if there is no such process.
|
|
boredzo@4043
|
84 |
+ (NSBundle *) runningHelperAppBundle {
|
|
boredzo@4049
|
85 |
return [self bundleForProcessWithBundleIdentifier:GROWL_HELPERAPP_BUNDLE_IDENTIFIER];
|
|
boredzo@4043
|
86 |
}
|
|
boredzo@4043
|
87 |
|
|
boredzo@2677
|
88 |
+ (NSBundle *) growlPrefPaneBundle {
|
|
ingmarstein@1791
|
89 |
NSArray *librarySearchPaths;
|
|
ingmarstein@1791
|
90 |
NSString *path;
|
|
ingmarstein@1791
|
91 |
NSString *bundleIdentifier;
|
|
ingmarstein@1791
|
92 |
NSEnumerator *searchPathEnumerator;
|
|
ingmarstein@1791
|
93 |
NSBundle *bundle;
|
|
ingmarstein@1791
|
94 |
|
|
ingmarstein@2591
|
95 |
if (prefPaneBundle)
|
|
ingmarstein@2591
|
96 |
return prefPaneBundle;
|
|
ingmarstein@2327
|
97 |
|
|
ingmarstein@2327
|
98 |
prefPaneBundle = [NSBundle bundleWithIdentifier:GROWL_PREFPANE_BUNDLE_IDENTIFIER];
|
|
ingmarstein@2591
|
99 |
if (prefPaneBundle)
|
|
ingmarstein@2591
|
100 |
return prefPaneBundle;
|
|
ingmarstein@1791
|
101 |
|
|
boredzo@4043
|
102 |
//If GHA is running, the prefpane bundle is the bundle that contains it.
|
|
boredzo@4043
|
103 |
NSBundle *runningHelperAppBundle = [self runningHelperAppBundle];
|
|
boredzo@4043
|
104 |
NSString *runningHelperAppBundlePath = [runningHelperAppBundle bundlePath];
|
|
boredzo@4043
|
105 |
//GHA in Growl.prefPane/Contents/Resources/
|
|
boredzo@4043
|
106 |
NSString *possiblePrefPaneBundlePath1 = [runningHelperAppBundlePath stringByDeletingLastPathComponent];
|
|
boredzo@4043
|
107 |
//GHA in Growl.prefPane/ (hypothetical)
|
|
boredzo@4043
|
108 |
NSString *possiblePrefPaneBundlePath2 = [[possiblePrefPaneBundlePath1 stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
|
|
boredzo@4043
|
109 |
if ([[[possiblePrefPaneBundlePath1 pathExtension] lowercaseString] isEqualToString:@"prefpane"]) {
|
|
boredzo@4043
|
110 |
prefPaneBundle = [NSBundle bundleWithPath:possiblePrefPaneBundlePath1];
|
|
boredzo@4043
|
111 |
if (prefPaneBundle)
|
|
boredzo@4043
|
112 |
return prefPaneBundle;
|
|
boredzo@4043
|
113 |
}
|
|
boredzo@4043
|
114 |
if ([[[possiblePrefPaneBundlePath2 pathExtension] lowercaseString] isEqualToString:@"prefpane"]) {
|
|
boredzo@4043
|
115 |
prefPaneBundle = [NSBundle bundleWithPath:possiblePrefPaneBundlePath2];
|
|
boredzo@4043
|
116 |
if (prefPaneBundle)
|
|
boredzo@4043
|
117 |
return prefPaneBundle;
|
|
boredzo@4043
|
118 |
}
|
|
boredzo@4043
|
119 |
|
|
ingmarstein@1791
|
120 |
static const unsigned bundleIDComparisonFlags = NSCaseInsensitiveSearch | NSBackwardsSearch;
|
|
ingmarstein@1791
|
121 |
|
|
ingmarstein@2327
|
122 |
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
ingmarstein@2327
|
123 |
|
|
ingmarstein@1791
|
124 |
//Find Library directories in all domains except /System (as of Panther, that's ~/Library, /Library, and /Network/Library)
|
|
ingmarstein@1791
|
125 |
librarySearchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask & ~NSSystemDomainMask, YES);
|
|
ingmarstein@1791
|
126 |
|
|
boredzo@2467
|
127 |
/*First up, we'll look for Growl.prefPane, and if it exists, check whether
|
|
boredzo@2467
|
128 |
* it is our prefPane.
|
|
ingmarstein@1791
|
129 |
*This is much faster than having to enumerate all preference panes, and
|
|
ingmarstein@1791
|
130 |
* can drop a significant amount of time off this code.
|
|
ingmarstein@1791
|
131 |
*/
|
|
ingmarstein@1791
|
132 |
searchPathEnumerator = [librarySearchPaths objectEnumerator];
|
|
ingmarstein@1791
|
133 |
while ((path = [searchPathEnumerator nextObject])) {
|
|
ingmarstein@1791
|
134 |
path = [path stringByAppendingPathComponent:PREFERENCE_PANES_SUBFOLDER_OF_LIBRARY];
|
|
ingmarstein@1791
|
135 |
path = [path stringByAppendingPathComponent:GROWL_PREFPANE_NAME];
|
|
ingmarstein@1791
|
136 |
|
|
ingmarstein@2327
|
137 |
if ([fileManager fileExistsAtPath:path]) {
|
|
ingmarstein@1791
|
138 |
bundle = [NSBundle bundleWithPath:path];
|
|
ingmarstein@1791
|
139 |
|
|
ingmarstein@1791
|
140 |
if (bundle) {
|
|
ingmarstein@1791
|
141 |
bundleIdentifier = [bundle bundleIdentifier];
|
|
ingmarstein@1791
|
142 |
|
|
ingmarstein@1791
|
143 |
if (bundleIdentifier && ([bundleIdentifier compare:GROWL_PREFPANE_BUNDLE_IDENTIFIER options:bundleIDComparisonFlags] == NSOrderedSame)) {
|
|
ingmarstein@1791
|
144 |
prefPaneBundle = bundle;
|
|
ingmarstein@1791
|
145 |
return prefPaneBundle;
|
|
ingmarstein@1791
|
146 |
}
|
|
ingmarstein@1791
|
147 |
}
|
|
ingmarstein@1791
|
148 |
}
|
|
ingmarstein@1791
|
149 |
}
|
|
ingmarstein@1791
|
150 |
|
|
ingmarstein@1791
|
151 |
/*Enumerate all installed preference panes, looking for the Growl prefpane
|
|
ingmarstein@1791
|
152 |
* bundle identifier and stopping when we find it.
|
|
ingmarstein@1791
|
153 |
*Note that we check the bundle identifier because we should not insist
|
|
ingmarstein@1791
|
154 |
* that the user not rename his preference pane files, although most users
|
|
ingmarstein@1791
|
155 |
* of course will not. If the user wants to mutilate the Info.plist file
|
|
ingmarstein@1791
|
156 |
* inside the bundle, he/she deserves to not have a working Growl
|
|
ingmarstein@1791
|
157 |
* installation.
|
|
ingmarstein@1791
|
158 |
*/
|
|
ingmarstein@1791
|
159 |
searchPathEnumerator = [librarySearchPaths objectEnumerator];
|
|
ingmarstein@1791
|
160 |
while ((path = [searchPathEnumerator nextObject])) {
|
|
ingmarstein@1791
|
161 |
NSString *bundlePath;
|
|
ingmarstein@1791
|
162 |
NSDirectoryEnumerator *bundleEnum;
|
|
ingmarstein@1791
|
163 |
|
|
ingmarstein@1791
|
164 |
path = [path stringByAppendingPathComponent:PREFERENCE_PANES_SUBFOLDER_OF_LIBRARY];
|
|
ingmarstein@2327
|
165 |
bundleEnum = [fileManager enumeratorAtPath:path];
|
|
ingmarstein@1791
|
166 |
|
|
ingmarstein@1791
|
167 |
while ((bundlePath = [bundleEnum nextObject])) {
|
|
ingmarstein@1791
|
168 |
if ([[bundlePath pathExtension] isEqualToString:PREFERENCE_PANE_EXTENSION]) {
|
|
ingmarstein@1791
|
169 |
bundle = [NSBundle bundleWithPath:[path stringByAppendingPathComponent:bundlePath]];
|
|
ingmarstein@1791
|
170 |
|
|
ingmarstein@1791
|
171 |
if (bundle) {
|
|
ingmarstein@1791
|
172 |
bundleIdentifier = [bundle bundleIdentifier];
|
|
ingmarstein@1791
|
173 |
|
|
ingmarstein@1791
|
174 |
if (bundleIdentifier && ([bundleIdentifier compare:GROWL_PREFPANE_BUNDLE_IDENTIFIER options:bundleIDComparisonFlags] == NSOrderedSame)) {
|
|
ingmarstein@1791
|
175 |
prefPaneBundle = bundle;
|
|
ingmarstein@1791
|
176 |
return prefPaneBundle;
|
|
ingmarstein@1791
|
177 |
}
|
|
ingmarstein@1791
|
178 |
}
|
|
ingmarstein@1791
|
179 |
|
|
ingmarstein@1791
|
180 |
[bundleEnum skipDescendents];
|
|
ingmarstein@1791
|
181 |
}
|
|
ingmarstein@1791
|
182 |
}
|
|
ingmarstein@1791
|
183 |
}
|
|
ingmarstein@1791
|
184 |
|
|
ingmarstein@1791
|
185 |
return nil;
|
|
ingmarstein@1791
|
186 |
}
|
|
ingmarstein@1791
|
187 |
|
|
boredzo@2677
|
188 |
+ (NSBundle *) helperAppBundle {
|
|
ingmarstein@1791
|
189 |
if (!helperAppBundle) {
|
|
boredzo@4043
|
190 |
helperAppBundle = [self runningHelperAppBundle];
|
|
ingmarstein@2591
|
191 |
if (!helperAppBundle) {
|
|
ingmarstein@1791
|
192 |
//look in the prefpane bundle.
|
|
boredzo@2677
|
193 |
NSBundle *bundle = [GrowlPathUtilities growlPrefPaneBundle];
|
|
ingmarstein@1791
|
194 |
NSString *helperAppPath = [bundle pathForResource:@"GrowlHelperApp" ofType:@"app"];
|
|
ingmarstein@1791
|
195 |
helperAppBundle = [NSBundle bundleWithPath:helperAppPath];
|
|
ingmarstein@1791
|
196 |
}
|
|
ingmarstein@1791
|
197 |
}
|
|
ingmarstein@1791
|
198 |
return helperAppBundle;
|
|
ingmarstein@1791
|
199 |
}
|
|
ingmarstein@1791
|
200 |
|
|
ingmarstein@1907
|
201 |
#pragma mark -
|
|
boredzo@2467
|
202 |
#pragma mark Directories
|
|
boredzo@2467
|
203 |
|
|
boredzo@2677
|
204 |
+ (NSArray *) searchPathForDirectory:(GrowlSearchPathDirectory) directory inDomains:(GrowlSearchPathDomainMask) domainMask mustBeWritable:(BOOL)flag {
|
|
boredzo@2467
|
205 |
if (directory < GrowlSupportDirectory) {
|
|
boredzo@2467
|
206 |
NSArray *searchPath = NSSearchPathForDirectoriesInDomains(directory, domainMask, /*expandTilde*/ YES);
|
|
boredzo@2677
|
207 |
if (!flag)
|
|
boredzo@2467
|
208 |
return searchPath;
|
|
boredzo@2467
|
209 |
else {
|
|
boredzo@2467
|
210 |
//flag is not NO: exclude non-writable directories.
|
|
boredzo@2467
|
211 |
NSMutableArray *result = [NSMutableArray arrayWithCapacity:[searchPath count]];
|
|
boredzo@2467
|
212 |
NSFileManager *mgr = [NSFileManager defaultManager];
|
|
boredzo@2467
|
213 |
|
|
boredzo@2467
|
214 |
NSEnumerator *searchPathEnum = [searchPath objectEnumerator];
|
|
boredzo@2467
|
215 |
NSString *dir;
|
|
boredzo@2677
|
216 |
while ((dir = [searchPathEnum nextObject])) {
|
|
ingmarstein@2470
|
217 |
if ([mgr isWritableFileAtPath:dir])
|
|
boredzo@2467
|
218 |
[result addObject:dir];
|
|
boredzo@2677
|
219 |
}
|
|
boredzo@2467
|
220 |
|
|
boredzo@2467
|
221 |
return result;
|
|
boredzo@2467
|
222 |
}
|
|
boredzo@2467
|
223 |
} else {
|
|
boredzo@2467
|
224 |
//determine what to append to each Application Support folder.
|
|
boredzo@2467
|
225 |
NSString *subpath = nil;
|
|
boredzo@2467
|
226 |
switch (directory) {
|
|
boredzo@2467
|
227 |
case GrowlSupportDirectory:
|
|
boredzo@2467
|
228 |
//do nothing.
|
|
boredzo@2467
|
229 |
break;
|
|
boredzo@2467
|
230 |
|
|
boredzo@2467
|
231 |
case GrowlScreenshotsDirectory:
|
|
boredzo@2467
|
232 |
subpath = NAME_OF_SCREENSHOTS_DIRECTORY;
|
|
boredzo@2467
|
233 |
break;
|
|
boredzo@2467
|
234 |
|
|
boredzo@2467
|
235 |
case GrowlTicketsDirectory:
|
|
boredzo@2467
|
236 |
subpath = NAME_OF_TICKETS_DIRECTORY;
|
|
boredzo@2467
|
237 |
break;
|
|
boredzo@2467
|
238 |
|
|
boredzo@2477
|
239 |
case GrowlPluginsDirectory:
|
|
boredzo@2477
|
240 |
subpath = NAME_OF_PLUGINS_DIRECTORY;
|
|
boredzo@2477
|
241 |
break;
|
|
boredzo@2477
|
242 |
|
|
boredzo@2467
|
243 |
default:
|
|
boredzo@2467
|
244 |
NSLog(@"ERROR: GrowlPathUtil was asked for directory 0x%x, but it doesn't know what directory that is. Please tell the Growl developers.", directory);
|
|
boredzo@2467
|
245 |
return nil;
|
|
boredzo@2467
|
246 |
}
|
|
boredzo@2467
|
247 |
if (subpath)
|
|
boredzo@2467
|
248 |
subpath = [@"Application Support/Growl" stringByAppendingPathComponent:subpath];
|
|
boredzo@2467
|
249 |
else
|
|
boredzo@2467
|
250 |
subpath = @"Application Support/Growl";
|
|
boredzo@2467
|
251 |
|
|
boredzo@2467
|
252 |
/*get the search path, and append the subpath to all the items therein.
|
|
boredzo@2467
|
253 |
*exclude results that don't exist.
|
|
boredzo@2467
|
254 |
*/
|
|
boredzo@2467
|
255 |
NSFileManager *mgr = [NSFileManager defaultManager];
|
|
boredzo@2467
|
256 |
BOOL isDir = NO;
|
|
boredzo@2467
|
257 |
|
|
boredzo@2467
|
258 |
NSArray *searchPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, domainMask, /*expandTilde*/ YES);
|
|
boredzo@2467
|
259 |
NSMutableArray *mSearchPath = [NSMutableArray arrayWithCapacity:[searchPath count]];
|
|
boredzo@2467
|
260 |
NSEnumerator *searchPathEnum = [searchPath objectEnumerator];
|
|
boredzo@2467
|
261 |
NSString *path;
|
|
boredzo@2467
|
262 |
while ((path = [searchPathEnum nextObject])) {
|
|
boredzo@2467
|
263 |
path = [path stringByAppendingPathComponent:subpath];
|
|
ingmarstein@2470
|
264 |
if ([mgr fileExistsAtPath:path isDirectory:&isDir] && isDir)
|
|
boredzo@2467
|
265 |
[mSearchPath addObject:path];
|
|
boredzo@2467
|
266 |
}
|
|
boredzo@2467
|
267 |
|
|
boredzo@2467
|
268 |
return mSearchPath;
|
|
boredzo@2467
|
269 |
}
|
|
boredzo@2467
|
270 |
}
|
|
boredzo@2467
|
271 |
|
|
boredzo@2677
|
272 |
+ (NSArray *) searchPathForDirectory:(GrowlSearchPathDirectory) directory inDomains:(GrowlSearchPathDomainMask) domainMask {
|
|
boredzo@2467
|
273 |
//NO to emulate the default NSSearchPathForDirectoriesInDomains behaviour.
|
|
boredzo@2677
|
274 |
return [self searchPathForDirectory:directory inDomains:domainMask mustBeWritable:NO];
|
|
boredzo@2677
|
275 |
}
|
|
boredzo@2677
|
276 |
|
|
boredzo@2677
|
277 |
+ (NSString *) growlSupportDirectory {
|
|
boredzo@2677
|
278 |
NSArray *searchPath = [self searchPathForDirectory:GrowlSupportDirectory inDomains:NSUserDomainMask mustBeWritable:YES];
|
|
ingmarstein@2470
|
279 |
if ([searchPath count])
|
|
boredzo@2467
|
280 |
return [searchPath objectAtIndex:0U];
|
|
boredzo@2467
|
281 |
else {
|
|
boredzo@2467
|
282 |
NSString *path = nil;
|
|
ingmarstein@2470
|
283 |
|
|
boredzo@2467
|
284 |
//if this doesn't return any writable directories, path will still be nil.
|
|
boredzo@2677
|
285 |
searchPath = [self searchPathForDirectory:NSLibraryDirectory inDomains:NSAllDomainsMask mustBeWritable:YES];
|
|
ingmarstein@2470
|
286 |
if ([searchPath count]) {
|
|
ingmarstein@2472
|
287 |
path = [[searchPath objectAtIndex:0U] stringByAppendingPathComponent:@"Application Support/Growl"];
|
|
boredzo@2467
|
288 |
//try to create it. if that doesn't work, don't return it. return nil instead.
|
|
ingmarstein@2470
|
289 |
if (![[NSFileManager defaultManager] createDirectoryAtPath:path attributes:nil])
|
|
boredzo@2467
|
290 |
path = nil;
|
|
boredzo@2467
|
291 |
}
|
|
ingmarstein@2472
|
292 |
|
|
boredzo@2467
|
293 |
return path;
|
|
boredzo@2467
|
294 |
}
|
|
boredzo@2467
|
295 |
}
|
|
ingmarstein@1907
|
296 |
|
|
boredzo@2677
|
297 |
+ (NSString *) screenshotsDirectory {
|
|
boredzo@2677
|
298 |
NSArray *searchPath = [self searchPathForDirectory:GrowlScreenshotsDirectory inDomains:NSAllDomainsMask mustBeWritable:YES];
|
|
ingmarstein@2470
|
299 |
if ([searchPath count])
|
|
boredzo@2467
|
300 |
return [searchPath objectAtIndex:0U];
|
|
boredzo@2467
|
301 |
else {
|
|
boredzo@2467
|
302 |
NSString *path = nil;
|
|
boredzo@2467
|
303 |
|
|
boredzo@2467
|
304 |
//if this doesn't return any writable directories, path will still be nil.
|
|
boredzo@2677
|
305 |
path = [self growlSupportDirectory];
|
|
ingmarstein@2472
|
306 |
if (path) {
|
|
ingmarstein@2472
|
307 |
path = [path stringByAppendingPathComponent:NAME_OF_SCREENSHOTS_DIRECTORY];
|
|
boredzo@2467
|
308 |
//try to create it. if that doesn't work, don't return it. return nil instead.
|
|
ingmarstein@2470
|
309 |
if (![[NSFileManager defaultManager] createDirectoryAtPath:path attributes:nil])
|
|
boredzo@2467
|
310 |
path = nil;
|
|
boredzo@2467
|
311 |
}
|
|
boredzo@2467
|
312 |
|
|
boredzo@2467
|
313 |
return path;
|
|
boredzo@2467
|
314 |
}
|
|
boredzo@2467
|
315 |
}
|
|
boredzo@2467
|
316 |
|
|
boredzo@2677
|
317 |
+ (NSString *) ticketsDirectory {
|
|
boredzo@2677
|
318 |
NSArray *searchPath = [self searchPathForDirectory:GrowlTicketsDirectory inDomains:NSAllDomainsMask mustBeWritable:YES];
|
|
ingmarstein@2470
|
319 |
if ([searchPath count])
|
|
boredzo@2467
|
320 |
return [searchPath objectAtIndex:0U];
|
|
boredzo@2467
|
321 |
else {
|
|
boredzo@2467
|
322 |
NSString *path = nil;
|
|
ingmarstein@2591
|
323 |
|
|
boredzo@2467
|
324 |
//if this doesn't return any writable directories, path will still be nil.
|
|
boredzo@2677
|
325 |
path = [self growlSupportDirectory];
|
|
ingmarstein@2472
|
326 |
if (path) {
|
|
ingmarstein@2472
|
327 |
path = [path stringByAppendingPathComponent:NAME_OF_TICKETS_DIRECTORY];
|
|
boredzo@2467
|
328 |
//try to create it. if that doesn't work, don't return it. return nil instead.
|
|
ingmarstein@2470
|
329 |
if (![[NSFileManager defaultManager] createDirectoryAtPath:path attributes:nil])
|
|
boredzo@2467
|
330 |
path = nil;
|
|
boredzo@2467
|
331 |
}
|
|
ingmarstein@2591
|
332 |
|
|
boredzo@2467
|
333 |
return path;
|
|
boredzo@2467
|
334 |
}
|
|
boredzo@2467
|
335 |
}
|
|
boredzo@2467
|
336 |
|
|
boredzo@2467
|
337 |
#pragma mark -
|
|
boredzo@2467
|
338 |
#pragma mark Screenshot names
|
|
ingmarstein@1907
|
339 |
|
|
boredzo@2677
|
340 |
+ (NSString *) nextScreenshotName {
|
|
boredzo@2677
|
341 |
return [self nextScreenshotNameInDirectory:nil];
|
|
boredzo@2677
|
342 |
}
|
|
boredzo@2677
|
343 |
|
|
boredzo@2677
|
344 |
+ (NSString *) nextScreenshotNameInDirectory:(NSString *) directory {
|
|
ingmarstein@1907
|
345 |
NSFileManager *mgr = [NSFileManager defaultManager];
|
|
ingmarstein@1907
|
346 |
|
|
boredzo@2467
|
347 |
if (!directory)
|
|
boredzo@2677
|
348 |
directory = [GrowlPathUtilities screenshotsDirectory];
|
|
boredzo@2467
|
349 |
|
|
boredzo@2467
|
350 |
//build a set of all the files in the directory, without their filename extensions.
|
|
ingmarstein@1907
|
351 |
NSArray *origContents = [mgr directoryContentsAtPath:directory];
|
|
ingmarstein@1907
|
352 |
NSMutableSet *directoryContents = [[NSMutableSet alloc] initWithCapacity:[origContents count]];
|
|
ingmarstein@1907
|
353 |
|
|
ingmarstein@1907
|
354 |
NSEnumerator *filesEnum = [origContents objectEnumerator];
|
|
ingmarstein@1907
|
355 |
NSString *existingFilename;
|
|
boredzo@2467
|
356 |
while ((existingFilename = [filesEnum nextObject]))
|
|
boredzo@2467
|
357 |
[directoryContents addObject:[existingFilename stringByDeletingPathExtension]];
|
|
boredzo@2467
|
358 |
|
|
boredzo@2467
|
359 |
//look for a filename that doesn't exist (with any extension) in the directory.
|
|
boredzo@2467
|
360 |
NSString *filename = nil;
|
|
sdwilsh@3322
|
361 |
unsigned long long i;
|
|
sdwilsh@3323
|
362 |
for (i = 1ULL; i < ULLONG_MAX; ++i) {
|
|
ingmarstein@1907
|
363 |
[filename release];
|
|
boredzo@2467
|
364 |
filename = [[NSString alloc] initWithFormat:@"Screenshot %llu", i];
|
|
boredzo@2467
|
365 |
if (![directoryContents containsObject:filename])
|
|
ingmarstein@1907
|
366 |
break;
|
|
ingmarstein@1907
|
367 |
}
|
|
ingmarstein@1907
|
368 |
[directoryContents release];
|
|
ingmarstein@1907
|
369 |
|
|
ingmarstein@1907
|
370 |
return [filename autorelease];
|
|
ingmarstein@1907
|
371 |
}
|
|
ingmarstein@1907
|
372 |
|
|
boredzo@2467
|
373 |
#pragma mark -
|
|
boredzo@2467
|
374 |
#pragma mark Tickets
|
|
boredzo@2467
|
375 |
|
|
boredzo@2677
|
376 |
+ (NSString *) defaultSavePathForTicketWithApplicationName:(NSString *) appName {
|
|
boredzo@2677
|
377 |
return [[self ticketsDirectory] stringByAppendingPathComponent:[appName stringByAppendingPathExtension:GROWL_PATHEXTENSION_TICKET]];
|
|
boredzo@2677
|
378 |
}
|
|
boredzo@2677
|
379 |
|
|
boredzo@2677
|
380 |
@end
|