1/* Recycler.m 2 * 3 * Copyright (C) 2004-2016 Free Software Foundation, Inc. 4 * 5 * Author: Enrico Sersale <enrico@imago.ro> 6 * Date: June 2004 7 * 8 * This file is part of the GNUstep Recycler application 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. 23 */ 24 25#import <Foundation/Foundation.h> 26#import <AppKit/AppKit.h> 27#import <GNUstepBase/GNUstep.h> 28 29#import "Recycler.h" 30#import "RecyclerView.h" 31#import "Preferences/RecyclerPrefs.h" 32#import "Dialogs/StartAppWin.h" 33#import "FSNode.h" 34#import "FSNodeRep.h" 35#import "FSNFunctions.h" 36 37 38static Recycler *recycler = nil; 39 40@implementation Recycler 41 42+ (Recycler *)recycler 43{ 44 if (recycler == nil) { 45 recycler = [[Recycler alloc] init]; 46 } 47 return recycler; 48} 49 50+ (void)initialize 51{ 52 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 53 [defaults setObject: @"Recycler" 54 forKey: @"DesktopApplicationName"]; 55 [defaults setObject: @"recycler" 56 forKey: @"DesktopApplicationSelName"]; 57 [defaults synchronize]; 58} 59 60- (void)dealloc 61{ 62 if (fswatcher && [[(NSDistantObject *)fswatcher connectionForProxy] isValid]) { 63 [fswatcher unregisterClient: (id <FSWClientProtocol>)self]; 64 DESTROY (fswatcher); 65 } 66 [[NSDistributedNotificationCenter defaultCenter] removeObserver: self]; 67 DESTROY (workspaceApplication); 68 RELEASE (trashPath); 69 RELEASE (recview); 70 RELEASE (preferences); 71 RELEASE (startAppWin); 72 73 [super dealloc]; 74} 75 76- (void)applicationWillFinishLaunching:(NSNotification *)aNotification 77{ 78 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 79 id entry; 80 81 fm = [NSFileManager defaultManager]; 82 ws = [NSWorkspace sharedWorkspace]; 83 nc = [NSNotificationCenter defaultCenter]; 84 fsnodeRep = [FSNodeRep sharedInstance]; 85 workspaceApplication = nil; 86 87 entry = [defaults objectForKey: @"reserved_names"]; 88 if (entry) { 89 [fsnodeRep setReservedNames: entry]; 90 } else { 91 [fsnodeRep setReservedNames: [NSArray arrayWithObjects: @".gwdir", @".gwsort", nil]]; 92 } 93} 94 95- (void)applicationDidFinishLaunching:(NSNotification *)aNotification 96{ 97 NSString *tpath; 98 BOOL isdir; 99 100 tpath = [NSHomeDirectory() stringByAppendingPathComponent: @".Trash"]; 101 102 if ([fm fileExistsAtPath: tpath isDirectory: &isdir] == NO) { 103 if ([fm createDirectoryAtPath: tpath attributes: nil] == NO) { 104 NSLog(@"Can't create the Recycler directory! Quitting now."); 105 [NSApp terminate: self]; 106 } 107 } 108 109 ASSIGN (trashPath, tpath); 110 111 fswatcher = nil; 112 fswnotifications = YES; 113 [self connectFSWatcher]; 114 115 docked = [[NSUserDefaults standardUserDefaults] boolForKey: @"docked"]; 116 117 if (docked) { 118 recview = [[RecyclerView alloc] init]; 119 [[[NSApp iconWindow] contentView] addSubview: recview]; 120 } else { 121 [NSApp setApplicationIconImage: [NSApp applicationIconImage]]; 122 recview = [[RecyclerView alloc] initWithWindow]; 123 [recview activate]; 124 } 125 126 preferences = [RecyclerPrefs new]; 127 128 startAppWin = [[StartAppWin alloc] init]; 129 130 [self addWatcherForPath: trashPath]; 131 132 [[NSDistributedNotificationCenter defaultCenter] addObserver: self 133 selector: @selector(fileSystemDidChange:) 134 name: @"GWFileSystemDidChangeNotification" 135 object: nil]; 136 137 terminating = NO; 138} 139 140- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app 141{ 142 terminating = YES; 143 144 [self removeWatcherForPath: trashPath]; 145 146 if (fswatcher) { 147 NSConnection *fswconn = [(NSDistantObject *)fswatcher connectionForProxy]; 148 149 if ([fswconn isValid]) { 150 [nc removeObserver: self 151 name: NSConnectionDidDieNotification 152 object: fswconn]; 153 [fswatcher unregisterClient: (id <FSWClientProtocol>)self]; 154 DESTROY (fswatcher); 155 } 156 } 157 158 [self updateDefaults]; 159 return NSTerminateNow; 160} 161 162- (oneway void)emptyTrash 163{ 164 [self emptyTrashFromMenu: nil]; 165} 166 167- (void)setDocked:(BOOL)value 168{ 169 docked = value; 170 171 if (docked) { 172 [[recview window] close]; 173 DESTROY (recview); 174 recview = [[RecyclerView alloc] init]; 175 [[[NSApp iconWindow] contentView] addSubview: recview]; 176 177 } else { 178 [recview removeFromSuperview]; 179 DESTROY (recview); 180 recview = [[RecyclerView alloc] initWithWindow]; 181 [recview activate]; 182 } 183} 184 185- (BOOL)isDocked 186{ 187 return docked; 188} 189 190- (void)fileSystemDidChange:(NSNotification *)notif 191{ 192 NSDictionary *dict = [notif userInfo]; 193 [recview nodeContentsDidChange: dict]; 194} 195 196- (void)watchedPathDidChange:(NSData *)dirinfo 197{ 198 NSDictionary *info = [NSUnarchiver unarchiveObjectWithData: dirinfo]; 199 [recview watchedPathChanged: info]; 200} 201 202- (void)updateDefaults 203{ 204 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 205 206 [defaults setBool: docked forKey: @"docked"]; 207 [defaults synchronize]; 208 [recview updateDefaults]; 209 [preferences updateDefaults]; 210} 211 212- (void)contactWorkspaceApp 213{ 214 if (workspaceApplication == nil) { 215 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 216 NSString *appName = [defaults stringForKey: @"GSWorkspaceApplication"]; 217 218 if (appName == nil) { 219 appName = @"GWorkspace"; 220 } 221 222 workspaceApplication = [NSConnection rootProxyForConnectionWithRegisteredName: appName 223 host: @""]; 224 if (workspaceApplication == nil) { 225 int i; 226 227 [ws launchApplication: appName]; 228 229 for (i = 0; i < 80; i++) { 230 [[NSRunLoop currentRunLoop] runUntilDate: 231 [NSDate dateWithTimeIntervalSinceNow: 0.1]]; 232 233 workspaceApplication = [NSConnection rootProxyForConnectionWithRegisteredName: appName 234 host: @""]; 235 if (workspaceApplication) { 236 break; 237 } 238 } 239 } 240 241 if (workspaceApplication) { 242 [workspaceApplication setProtocolForProxy: @protocol(workspaceAppProtocol)]; 243 RETAIN (workspaceApplication); 244 245 [nc addObserver: self 246 selector: @selector(workspaceAppConnectionDidDie:) 247 name: NSConnectionDidDieNotification 248 object: [workspaceApplication connectionForProxy]]; 249 } else { 250 NSRunAlertPanel(nil, 251 NSLocalizedString(@"unable to contact the workspace application!", @""), 252 NSLocalizedString(@"Ok", @""), 253 nil, 254 nil); 255 } 256 } 257} 258 259- (void)workspaceAppConnectionDidDie:(NSNotification *)notif 260{ 261 id connection = [notif object]; 262 263 [nc removeObserver: self 264 name: NSConnectionDidDieNotification 265 object: connection]; 266 267 NSAssert(connection == [workspaceApplication connectionForProxy], 268 NSInternalInconsistencyException); 269 DESTROY (workspaceApplication); 270} 271 272- (void)connectFSWatcher 273{ 274 if (fswatcher == nil) { 275 fswatcher = [NSConnection rootProxyForConnectionWithRegisteredName: @"fswatcher" 276 host: @""]; 277 278 if (fswatcher == nil) { 279 NSString *cmd; 280 int i; 281 282 cmd = [NSTask launchPathForTool: @"fswatcher"]; 283 284 [startAppWin showWindowWithTitle: @"Recycler" 285 appName: @"fswatcher" 286 maxProgValue: 40.0]; 287 288 [NSTask launchedTaskWithLaunchPath: cmd arguments: nil]; 289 290 for (i = 1; i <= 40; i++) { 291 [startAppWin updateProgressBy: 1.0]; 292 [[NSRunLoop currentRunLoop] runUntilDate: 293 [NSDate dateWithTimeIntervalSinceNow: 0.1]]; 294 295 fswatcher = [NSConnection rootProxyForConnectionWithRegisteredName: @"fswatcher" 296 host: @""]; 297 if (fswatcher) { 298 [startAppWin updateProgressBy: 40.0 - i]; 299 break; 300 } 301 } 302 303 [[startAppWin win] close]; 304 } 305 306 if (fswatcher) { 307 RETAIN (fswatcher); 308 [fswatcher setProtocolForProxy: @protocol(FSWatcherProtocol)]; 309 310 [[NSNotificationCenter defaultCenter] addObserver: self 311 selector: @selector(fswatcherConnectionDidDie:) 312 name: NSConnectionDidDieNotification 313 object: [fswatcher connectionForProxy]]; 314 315 [fswatcher registerClient: (id <FSWClientProtocol>)self 316 isGlobalWatcher: NO]; 317 } else { 318 fswnotifications = NO; 319 NSRunAlertPanel(nil, 320 NSLocalizedString(@"unable to contact fswatcher\nfswatcher notifications disabled!", @""), 321 NSLocalizedString(@"Ok", @""), 322 nil, 323 nil); 324 } 325 } 326} 327 328- (void)fswatcherConnectionDidDie:(NSNotification *)notif 329{ 330 id connection = [notif object]; 331 332 [nc removeObserver: self 333 name: NSConnectionDidDieNotification 334 object: connection]; 335 336 NSAssert(connection == [fswatcher connectionForProxy], 337 NSInternalInconsistencyException); 338 RELEASE (fswatcher); 339 fswatcher = nil; 340 341 if (NSRunAlertPanel(nil, 342 NSLocalizedString(@"The fswatcher connection died.\nDo you want to restart it?", @""), 343 NSLocalizedString(@"Yes", @""), 344 NSLocalizedString(@"No", @""), 345 nil)) { 346 [self connectFSWatcher]; 347 } else { 348 fswnotifications = NO; 349 NSRunAlertPanel(nil, 350 NSLocalizedString(@"fswatcher notifications disabled!", @""), 351 NSLocalizedString(@"Ok", @""), 352 nil, 353 nil); 354 } 355} 356 357// 358// NSServicesRequests protocol 359// 360- (id)validRequestorForSendType:(NSString *)sendType 361 returnType:(NSString *)returnType 362{ 363 BOOL sendOK = ((sendType == nil) || ([sendType isEqual: NSFilenamesPboardType])); 364 BOOL returnOK = ((returnType == nil) || [returnType isEqual: NSFilenamesPboardType]); 365 366 if (sendOK && returnOK) { 367 return self; 368 } 369 370 return nil; 371} 372 373- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard 374{ 375 return ([[pboard types] indexOfObject: NSFilenamesPboardType] != NSNotFound); 376} 377 378- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard 379 types:(NSArray *)types 380{ 381 return NO; 382} 383 384 385// 386// DesktopApplication protocol 387// 388- (void)selectionChanged:(NSArray *)newsel 389{ 390} 391 392- (void)openSelectionInNewViewer:(BOOL)newv 393{ 394} 395 396- (void)openSelectionWithApp:(id)sender 397{ 398} 399 400- (void)performFileOperation:(NSString *)operation 401 source:(NSString *)source 402 destination:(NSString *)destination 403 files:(NSArray *)files 404{ 405 NSInteger tag; 406 407 if ([ws performFileOperation: operation 408 source: source 409 destination: destination 410 files: files 411 tag: &tag] == NO) 412 { 413 NSRunAlertPanel(nil, 414 NSLocalizedString(@"Unable to contact GWorkspace", @""), 415 NSLocalizedString(@"OK", @""), nil, nil); 416 } 417} 418 419- (void)concludeRemoteFilesDragOperation:(NSData *)opinfo 420 atLocalPath:(NSString *)localdest 421{ 422} 423 424- (void)addWatcherForPath:(NSString *)path 425{ 426 if (fswnotifications) { 427 [self connectFSWatcher]; 428 [fswatcher client: self addWatcherForPath: path]; 429 } 430} 431 432- (void)removeWatcherForPath:(NSString *)path 433{ 434 if (fswnotifications) { 435 [self connectFSWatcher]; 436 [fswatcher client: self removeWatcherForPath: path]; 437 } 438} 439 440- (NSString *)trashPath 441{ 442 return trashPath; 443} 444 445- (id)workspaceApplication 446{ 447 if (workspaceApplication == nil) { 448 [self contactWorkspaceApp]; 449 } 450 return workspaceApplication; 451} 452 453- (oneway void)terminateApplication 454{ 455 [NSApp terminate: self]; 456} 457 458- (BOOL)terminating 459{ 460 return terminating; 461} 462 463 464// 465// Menu Operations 466// 467- (void)emptyTrashFromMenu:(id)sender 468{ 469 CREATE_AUTORELEASE_POOL(arp); 470 FSNode *node = [FSNode nodeWithPath: trashPath]; 471 NSMutableArray *subNodes = [[node subNodes] mutableCopy]; 472 int count = [subNodes count]; 473 int i; 474 475 for (i = 0; i < count; i++) { 476 FSNode *nd = [subNodes objectAtIndex: i]; 477 478 if ([nd isReserved]) { 479 [subNodes removeObjectAtIndex: i]; 480 i--; 481 count --; 482 } 483 } 484 485 if ([subNodes count]) { 486 NSMutableArray *files = [NSMutableArray array]; 487 488 for (i = 0; i < [subNodes count]; i++) { 489 [files addObject: [(FSNode *)[subNodes objectAtIndex: i] name]]; 490 } 491 492 [self performFileOperation: @"GWorkspaceEmptyRecyclerOperation" 493 source: trashPath 494 destination: trashPath 495 files: files]; 496 } 497 498 RELEASE (subNodes); 499 RELEASE (arp); 500} 501 502- (void)paste:(id)sender 503{ 504 NSPasteboard *pb = [NSPasteboard generalPasteboard]; 505 506 if ([[pb types] containsObject: NSFilenamesPboardType]) { 507 NSArray *sourcePaths = [pb propertyListForType: NSFilenamesPboardType]; 508 509 [self contactWorkspaceApp]; 510 511 if (workspaceApplication) { 512 BOOL cut = [(id <OperationProtocol>)workspaceApplication filenamesWasCut]; 513 514 if ([recview validatePasteOfFilenames: sourcePaths wasCut: cut]) { 515 NSString *source = [[sourcePaths objectAtIndex: 0] stringByDeletingLastPathComponent]; 516 NSString *destination = trashPath; 517 NSMutableArray *files = [NSMutableArray array]; 518 NSString *operation; 519 int i; 520 521 for (i = 0; i < [sourcePaths count]; i++) { 522 NSString *spath = [sourcePaths objectAtIndex: i]; 523 [files addObject: [spath lastPathComponent]]; 524 } 525 526 if (cut) { 527 if ([source isEqual: trashPath]) { 528 operation = @"GWorkspaceRecycleOutOperation"; 529 } else { 530 operation = NSWorkspaceMoveOperation; 531 } 532 } else { 533 operation = NSWorkspaceCopyOperation; 534 } 535 536 [self performFileOperation: operation 537 source: source 538 destination: destination 539 files: files]; 540 } 541 542 } else { 543 NSRunAlertPanel(nil, 544 NSLocalizedString(@"File operations disabled!", @""), 545 NSLocalizedString(@"OK", @""), nil, nil); 546 return; 547 } 548 549 } 550} 551 552- (void)showPreferences:(id)sender 553{ 554 [preferences activate]; 555} 556 557- (void)showInfo:(id)sender 558{ 559 [NSApp orderFrontStandardInfoPanel: self]; 560} 561 562@end 563 564 565