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