1/* FileOpInfo.m
2 *
3 * Copyright (C) 2004-2015 Free Software Foundation, Inc.
4 *
5 * Author: Enrico Sersale <enrico@imago.ro>
6 *         Riccardo Mottola <rm@gnu.org>
7 * Date: March 2004
8 *
9 * This file is part of the GNUstep GWorkspace application
10 *
11 * This program 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 program 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 program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA.
24 */
25
26#import <Foundation/Foundation.h>
27#import <AppKit/AppKit.h>
28#import <GNUstepBase/GNUstep.h>
29
30#import "FileOpInfo.h"
31#import "Operation.h"
32#import "Functions.h"
33
34
35#define PROGR_STEPS (100.0)
36static BOOL stopped = NO;
37static BOOL paused = NO;
38
39static NSString *nibName = @"FileOperationWin";
40
41@implementation FileOpInfo
42
43- (NSString *)description
44{
45  return [NSString stringWithFormat: @"%@ from: %@ to: %@", type, source, destination];
46}
47
48- (void)dealloc
49{
50  [nc removeObserver: self];
51
52  RELEASE (operationDict);
53  RELEASE (type);
54  RELEASE (source);
55  RELEASE (destination);
56  RELEASE (files);
57  RELEASE (procFiles);
58  RELEASE (dupfiles);
59  RELEASE (notifNames);
60  RELEASE (win);
61
62  DESTROY (executor);
63  DESTROY (execconn);
64
65  [super dealloc];
66}
67
68+ (id)operationOfType:(NSString *)tp
69                  ref:(int)rf
70               source:(NSString *)src
71          destination:(NSString *)dst
72                files:(NSArray *)fls
73         confirmation:(BOOL)conf
74            usewindow:(BOOL)uwnd
75              winrect:(NSRect)wrect
76           controller:(id)cntrl
77{
78  return AUTORELEASE ([[self alloc] initWithOperationType: tp ref: rf
79                                source: src destination: dst files: fls
80                                      confirmation: conf usewindow: uwnd
81                                        winrect: wrect controller: cntrl]);
82}
83
84- (id)initWithOperationType:(NSString *)tp
85                        ref:(int)rf
86                     source:(NSString *)src
87                destination:(NSString *)dst
88                      files:(NSArray *)fls
89               confirmation:(BOOL)conf
90                  usewindow:(BOOL)uwnd
91                    winrect:(NSRect)wrect
92                 controller:(id)cntrl
93{
94  self = [super init];
95
96  if (self)
97    {
98      win = nil;
99      showwin = uwnd;
100
101      if (showwin) {
102	if ([NSBundle loadNibNamed: nibName owner: self] == NO)
103	  {
104	    NSLog(@"failed to load %@!", nibName);
105	    DESTROY (self);
106	    return self;
107	  }
108
109      if (NSEqualRects(wrect, NSZeroRect) == NO) {
110        [win setFrame: wrect display: NO];
111      } else if ([win setFrameUsingName: @"fopinfo"] == NO) {
112        [win setFrame: NSMakeRect(300, 300, 282, 102) display: NO];
113      }
114
115      [fromLabel setStringValue: NSLocalizedString(@"From:", @"")];
116      [toLabel setStringValue: NSLocalizedString(@"To:", @"")];
117      [pauseButt setTitle: NSLocalizedString(@"Pause", @"")];
118      [stopButt setTitle: NSLocalizedString(@"Stop", @"")];
119    }
120
121    ref = rf;
122    controller = cntrl;
123    fm = [NSFileManager defaultManager];
124    nc = [NSNotificationCenter defaultCenter];
125    dnc = [NSDistributedNotificationCenter defaultCenter];
126
127    ASSIGN (type, tp);
128    ASSIGN (source, src);
129    ASSIGN (destination, dst);
130    files = [[NSMutableArray arrayWithArray:fls] retain];
131    procFiles = [[NSMutableArray alloc] init];
132
133    dupfiles = [NSMutableArray new];
134
135    if ([type isEqual: NSWorkspaceDuplicateOperation]) {
136      NSString *copystr = NSLocalizedString(@"_copy", @"");
137      unsigned i;
138
139      for (i = 0; i < [files count]; i++)
140	{
141	  NSDictionary *fdict = [files objectAtIndex: i];
142	  NSString *fname = [fdict objectForKey: @"name"];
143	  NSString *newname = [NSString stringWithString: fname];
144	  NSString *ext = [newname pathExtension];
145	  NSString *base = [newname stringByDeletingPathExtension];
146	  NSString *ntmp;
147	  NSString *destpath;
148	  NSUInteger count = 1;
149
150	  while (1)
151	    {
152	      if (count == 1)
153		{
154		  ntmp = [NSString stringWithFormat: @"%@%@", base, copystr];
155		  if ([ext length]) {
156		    ntmp = [ntmp stringByAppendingPathExtension: ext];
157		  }
158		} else
159		{
160		  ntmp = [NSString stringWithFormat: @"%@%@%i", base, copystr, count];
161		  if ([ext length]) {
162		    ntmp = [ntmp stringByAppendingPathExtension: ext];
163		  }
164		}
165	      destpath = [destination stringByAppendingPathComponent: ntmp];
166
167	    if ([fm fileExistsAtPath: destpath] == NO) {
168	      newname = ntmp;
169	      break;
170	    } else
171	      {
172		count++;
173	      }
174	  }
175
176	  [dupfiles addObject: newname];
177	}
178    }
179
180    operationDict = [NSMutableDictionary new];
181    [operationDict setObject: type forKey: @"operation"];
182    [operationDict setObject: [NSNumber numberWithInt: ref] forKey: @"ref"];
183    [operationDict setObject: source forKey: @"source"];
184    if (destination != nil)
185      [operationDict setObject: destination forKey: @"destination"];
186    [operationDict setObject: files forKey: @"files"];
187
188    confirm = conf;
189    executor = nil;
190    opdone = NO;
191    }
192
193  return self;
194}
195
196- (void)startOperation
197{
198  if (confirm)
199    {
200      NSString *title = nil;
201      NSString *msg = nil;
202      NSString *msg1 = nil;
203      NSString *msg2 = nil;
204      NSString *items;
205
206      if ([files count] > 1)
207        {
208          items = [NSString stringWithFormat: @"%lu %@", (unsigned long)[files count], NSLocalizedString(@"items", @"")];
209        }
210      else
211        {
212          items = NSLocalizedString(@"one item", @"");
213        }
214
215      if ([type isEqual: NSWorkspaceMoveOperation])
216        {
217          title = NSLocalizedString(@"Move", @"");
218          msg1 = [NSString stringWithFormat: @"%@ %@ %@: ",
219                           NSLocalizedString(@"Move", @""),
220                           items,
221                           NSLocalizedString(@"from", @"")];
222          msg2 = NSLocalizedString(@"\nto: ", @"");
223          msg = [NSString stringWithFormat: @"%@%@%@%@?", msg1, source, msg2, destination];
224        }
225      else if ([type isEqual: NSWorkspaceCopyOperation])
226        {
227          title = NSLocalizedString(@"Copy", @"");
228          msg1 = [NSString stringWithFormat: @"%@ %@ %@: ",
229                           NSLocalizedString(@"Copy", @""),
230                           items,
231                           NSLocalizedString(@"from", @"")];
232          msg2 = NSLocalizedString(@"\nto: ", @"");
233          msg = [NSString stringWithFormat: @"%@%@%@%@?", msg1, source, msg2, destination];
234        }
235      else if ([type isEqual: NSWorkspaceLinkOperation])
236        {
237          title = NSLocalizedString(@"Link", @"");
238          msg1 = [NSString stringWithFormat: @"%@ %@ %@: ",
239                           NSLocalizedString(@"Link", @""),
240                           items,
241                           NSLocalizedString(@"from", @"")];
242          msg2 = NSLocalizedString(@"\nto: ", @"");
243          msg = [NSString stringWithFormat: @"%@%@%@%@?", msg1, source, msg2, destination];
244        }
245      else if ([type isEqual: NSWorkspaceRecycleOperation])
246        {
247          title = NSLocalizedString(@"Recycler", @"");
248          msg1 = [NSString stringWithFormat: @"%@ %@ %@: ",
249                           NSLocalizedString(@"Move", @""),
250                           items,
251                           NSLocalizedString(@"from", @"")];
252          msg2 = NSLocalizedString(@"\nto the Recycler", @"");
253          msg = [NSString stringWithFormat: @"%@%@%@?", msg1, source, msg2];
254        }
255      else if ([type isEqual: @"GWorkspaceRecycleOutOperation"])
256        {
257          title = NSLocalizedString(@"Recycler", @"");
258          msg1 = [NSString stringWithFormat: @"%@ %@ %@ ",
259                           NSLocalizedString(@"Move", @""),
260                           items,
261                           NSLocalizedString(@"from the Recycler", @"")];
262          msg2 = NSLocalizedString(@"\nto: ", @"");
263          msg = [NSString stringWithFormat: @"%@%@%@?", msg1, msg2, destination];
264        }
265      else if ([type isEqual: @"GWorkspaceEmptyRecyclerOperation"])
266        {
267          title = NSLocalizedString(@"Recycler", @"");
268          msg = NSLocalizedString(@"Empty the Recycler?", @"");
269        }
270      else if ([type isEqual: NSWorkspaceDestroyOperation])
271        {
272          title = NSLocalizedString(@"Delete", @"");
273          msg = NSLocalizedString(@"Delete the selected objects?", @"");
274        }
275      else if ([type isEqual: NSWorkspaceDuplicateOperation])
276        {
277          title = NSLocalizedString(@"Duplicate", @"");
278          msg = NSLocalizedString(@"Duplicate the selected objects?", @"");
279        }
280
281      if (NSRunAlertPanel(title, msg,
282                          NSLocalizedString(@"OK", @""),
283                          NSLocalizedString(@"Cancel", @""),
284                          nil) != NSAlertDefaultReturn) {
285        [self endOperation];
286        return;
287      }
288    }
289  [self detachOperationThread];
290}
291
292- (void) threadWillExit: (NSNotification *)notification
293{
294  [nc removeObserver:self
295                name:NSThreadWillExitNotification
296              object:nil];
297
298  [nc removeObserver: self
299                name: NSConnectionDidDieNotification
300              object: execconn];
301
302  executor = nil;
303}
304
305-(void)detachOperationThread
306{
307  NSPort *port[2];
308  NSArray *ports;
309
310  port[0] = (NSPort *)[NSPort port];
311  port[1] = (NSPort *)[NSPort port];
312
313  ports = [NSArray arrayWithObjects: port[1], port[0], nil];
314
315  execconn = [[NSConnection alloc] initWithReceivePort: port[0]
316                                              sendPort: port[1]];
317  [execconn setRootObject: self];
318  [execconn setDelegate: self];
319
320  [nc addObserver: self
321         selector: @selector(connectionDidDie:)
322             name: NSConnectionDidDieNotification
323           object: execconn];
324
325  [nc addObserver: self
326         selector: @selector(threadWillExit:)
327             name: NSThreadWillExitNotification
328           object: nil];
329
330  NS_DURING
331    {
332      [NSThread detachNewThreadSelector: @selector(setPorts:)
333		                           toTarget: [FileOpExecutor class]
334		                         withObject: ports];
335    }
336  NS_HANDLER
337    {
338      NSRunAlertPanel(nil,
339                      NSLocalizedString(@"A fatal error occured while detaching the thread!", @""),
340                      NSLocalizedString(@"Continue", @""),
341                      nil,
342                      nil);
343      [self endOperation];
344    }
345  NS_ENDHANDLER
346}
347
348- (NSInteger)requestUserConfirmationWithMessage:(NSString *)message
349                                    title:(NSString *)title
350{
351  return NSRunAlertPanel(NSLocalizedString(title, @""),
352			 NSLocalizedString(message, @""),
353			 NSLocalizedString(@"Ok", @""),
354			 NSLocalizedString(@"Cancel", @""),
355                         nil);
356}
357
358- (NSInteger)showErrorAlertWithMessage:(NSString *)message
359{
360  return NSRunAlertPanel(nil,
361                         NSLocalizedString(message, @""),
362			 NSLocalizedString(@"Ok", @""),
363                         nil,
364                         nil);
365}
366
367- (IBAction)pause:(id)sender
368{
369  if (paused == NO)
370    {
371      [pauseButt setTitle: NSLocalizedString(@"Continue", @"")];
372      paused = YES;
373    }
374  else
375    {
376      [self detachOperationThread];
377      [pauseButt setTitle: NSLocalizedString(@"Pause", @"")];
378      paused = NO;
379    }
380}
381
382- (IBAction)stop:(id)sender
383{
384  if (paused)
385    {
386      [self endOperation];
387    }
388  stopped = YES;
389}
390
391- (void)removeProcessedFiles
392{
393  NSData *pFData;
394  NSArray *pFiles;
395  NSUInteger i;
396
397  pFData = [executor processedFiles];
398  pFiles = [NSUnarchiver unarchiveObjectWithData: pFData];
399
400  for (i = 0; i < [pFiles count]; i++)
401    {
402      NSDictionary *fi;
403      NSUInteger j;
404      BOOL found;
405
406      j = 0;
407      found = NO;
408      while (j < [files count] && !found)
409        {
410          fi = [files objectAtIndex:j];
411
412          if ([[pFiles objectAtIndex:i] isEqualTo:[fi objectForKey:@"name"]])
413            found = YES;
414          else
415            i++;
416        }
417      if (found)
418        {
419          [procFiles addObject:[files objectAtIndex:j]];
420          [files removeObjectAtIndex:j];
421        }
422    }
423}
424
425- (void)showProgressWin
426{
427  if ([win isVisible] == NO) {
428    if ([type isEqual: NSWorkspaceMoveOperation]) {
429      [win setTitle: NSLocalizedString(@"Move", @"")];
430      [fromLabel setStringValue: NSLocalizedString(@"From:", @"")];
431      [fromField setStringValue: relativePathFittingInField(fromField, source)];
432      [toLabel setStringValue: NSLocalizedString(@"To:", @"")];
433      [toField setStringValue: relativePathFittingInField(fromField, destination)];
434
435    } else if ([type isEqual: NSWorkspaceCopyOperation]) {
436      [win setTitle: NSLocalizedString(@"Copy", @"")];
437      [fromLabel setStringValue: NSLocalizedString(@"From:", @"")];
438      [fromField setStringValue: relativePathFittingInField(fromField, source)];
439      [toLabel setStringValue: NSLocalizedString(@"To:", @"")];
440      [toField setStringValue: relativePathFittingInField(fromField, destination)];
441
442    } else if ([type isEqual: NSWorkspaceLinkOperation]) {
443      [win setTitle: NSLocalizedString(@"Link", @"")];
444      [fromLabel setStringValue: NSLocalizedString(@"From:", @"")];
445      [fromField setStringValue: relativePathFittingInField(fromField, source)];
446      [toLabel setStringValue: NSLocalizedString(@"To:", @"")];
447      [toField setStringValue: relativePathFittingInField(fromField, destination)];
448
449    } else if ([type isEqual: NSWorkspaceDuplicateOperation]) {
450      [win setTitle: NSLocalizedString(@"Duplicate", @"")];
451      [fromLabel setStringValue: NSLocalizedString(@"In:", @"")];
452      [fromField setStringValue: relativePathFittingInField(fromField, destination)];
453      [toLabel setStringValue: @""];
454      [toField setStringValue: @""];
455
456    } else if ([type isEqual: NSWorkspaceDestroyOperation]) {
457      [win setTitle: NSLocalizedString(@"Destroy", @"")];
458      [fromLabel setStringValue: NSLocalizedString(@"In:", @"")];
459      [fromField setStringValue: relativePathFittingInField(fromField, destination)];
460      [toLabel setStringValue: @""];
461      [toField setStringValue: @""];
462
463    } else if ([type isEqual: NSWorkspaceRecycleOperation]) {
464      [win setTitle: NSLocalizedString(@"Move", @"")];
465      [fromLabel setStringValue: NSLocalizedString(@"From:", @"")];
466      [fromField setStringValue: relativePathFittingInField(fromField, source)];
467      [toLabel setStringValue: NSLocalizedString(@"To:", @"")];
468      [toField setStringValue: NSLocalizedString(@"the Recycler", @"")];
469
470    } else if ([type isEqual: @"GWorkspaceRecycleOutOperation"]) {
471      [win setTitle: NSLocalizedString(@"Move", @"")];
472      [fromLabel setStringValue: NSLocalizedString(@"From:", @"")];
473      [fromField setStringValue: NSLocalizedString(@"the Recycler", @"")];
474      [toLabel setStringValue: NSLocalizedString(@"To:", @"")];
475      [toField setStringValue: relativePathFittingInField(fromField, destination)];
476
477    } else if ([type isEqual: @"GWorkspaceEmptyRecyclerOperation"]) {
478      [win setTitle: NSLocalizedString(@"Destroy", @"")];
479      [fromLabel setStringValue: NSLocalizedString(@"In:", @"")];
480      [fromField setStringValue: NSLocalizedString(@"the Recycler", @"")];
481      [toLabel setStringValue: @""];
482      [toField setStringValue: @""];
483    }
484
485    [progInd setIndeterminate: YES];
486    [progInd startAnimation: self];
487  }
488
489  [win orderFront: nil];
490  showwin = YES;
491}
492
493- (void)setNumFiles:(int)n
494{
495  [progInd stopAnimation: self];
496  [progInd setIndeterminate: NO];
497  [progInd setMinValue: 0.0];
498  [progInd setMaxValue: n];
499  [progInd setDoubleValue: 0.0];
500}
501
502- (void)setProgIndicatorValue:(int)n
503{
504  [progInd setDoubleValue: n];
505}
506
507- (void)cleanUpExecutor
508{
509  if (executor)
510    {
511      [nc removeObserver: self
512                    name: NSConnectionDidDieNotification
513                  object: execconn];
514      [execconn setRootObject:nil];
515      DESTROY (executor);
516      DESTROY (execconn);
517    }
518}
519
520- (void)endOperation
521{
522  if (showwin)
523    {
524      if ([progInd isIndeterminate])
525        [progInd stopAnimation:self];
526
527      [win saveFrameUsingName: @"fopinfo"];
528      [win close];
529    }
530
531  [controller endOfFileOperation: self];
532  [execconn setRootObject:nil];
533}
534
535- (void)sendWillChangeNotification
536{
537  CREATE_AUTORELEASE_POOL(arp);
538  NSMutableDictionary *dict = [NSMutableDictionary dictionary];
539  NSUInteger i;
540
541  notifNames = [NSMutableArray new];
542
543  for (i = 0; i < [files count]; i++) {
544    NSDictionary *fdict = [files objectAtIndex: i];
545    NSString *name = [fdict objectForKey: @"name"];
546    [notifNames addObject: name];
547  }
548
549  [dict setObject: type forKey: @"operation"];
550  [dict setObject: source forKey: @"source"];
551  if (destination != nil)
552    [dict setObject: destination forKey: @"destination"];
553  [dict setObject: notifNames forKey: @"files"];
554
555  [nc postNotificationName: @"GWFileSystemWillChangeNotification" object: dict];
556
557  [dnc postNotificationName: @"GWFileSystemWillChangeNotification" object: nil userInfo: dict];
558  RELEASE (arp);
559}
560
561- (void)sendDidChangeNotification
562{
563  CREATE_AUTORELEASE_POOL(arp);
564  NSMutableDictionary *notifObj = [NSMutableDictionary dictionary];
565
566  [notifObj setObject: type forKey: @"operation"];
567  [notifObj setObject: source forKey: @"source"];
568  if (destination != nil)
569    [notifObj setObject: destination forKey: @"destination"];
570
571  if (executor) {
572    NSData *data = [executor processedFiles];
573    NSArray *processedFiles = [NSUnarchiver unarchiveObjectWithData: data];
574
575    [notifObj setObject: processedFiles forKey: @"files"];
576    [notifObj setObject: notifNames forKey: @"origfiles"];
577  } else {
578    [notifObj setObject: notifNames forKey: @"files"];
579    [notifObj setObject: notifNames forKey: @"origfiles"];
580  }
581
582  opdone = YES;
583
584  [nc postNotificationName: @"GWFileSystemDidChangeNotification" object: notifObj];
585
586  [dnc postNotificationName: @"GWFileSystemDidChangeNotification" object: nil userInfo: notifObj];
587  RELEASE (arp);
588}
589
590- (void)registerExecutor:(id)anObject
591{
592  NSData *opinfo = [NSArchiver archivedDataWithRootObject: operationDict];
593  BOOL samename;
594
595  [anObject setProtocolForProxy: @protocol(FileOpExecutorProtocol)];
596  executor = (id <FileOpExecutorProtocol>)[anObject retain];
597
598  [executor setOperation: opinfo];
599
600  if ([procFiles count] == 0)
601    {
602      samename = [executor checkSameName];
603
604      if (samename)
605        {
606          NSString *msg = nil;
607          NSString *title = nil;
608          int result;
609
610          onlyOlder = NO;
611          if ([type isEqual: NSWorkspaceMoveOperation])
612            {
613              msg = @"Some items have the same name;\ndo you want to replace them?";
614              title = @"Move";
615            }
616          else if ([type isEqual: NSWorkspaceCopyOperation])
617            {
618              msg = @"Some items have the same name;\ndo you want to replace them?";
619              title = @"Copy";
620            }
621          else if ([type isEqual: NSWorkspaceLinkOperation])
622            {
623              msg = @"Some items have the same name;\ndo you want to replace them?";
624              title = @"Link";
625            }
626          else if ([type isEqual: NSWorkspaceRecycleOperation])
627            {
628              msg = @"Some items have the same name;\ndo you want to replace them?";
629              title = @"Recycle";
630            }
631          else if ([type isEqual: @"GWorkspaceRecycleOutOperation"])
632            {
633              msg = @"Some items have the same name;\ndo you want to replace them?";
634              title = @"Recycle";
635            }
636
637          result = NSRunAlertPanel(NSLocalizedString(title, @""),							 NSLocalizedString(msg, @""),
638                                   NSLocalizedString(@"OK", @""),
639                                   NSLocalizedString(@"Cancel", @""),
640                                   NSLocalizedString(@"Only older", @""));
641
642          if (result == NSAlertAlternateReturn)
643            {
644              [controller endOfFileOperation: self];
645              return;
646            }
647          else if (result == NSAlertOtherReturn)
648            {
649              onlyOlder = YES;
650            }
651        }
652    }
653
654  [executor setOnlyOlder:onlyOlder];
655
656  if (showwin)
657    [self showProgressWin];
658
659  [self sendWillChangeNotification];
660
661  stopped = NO;
662  paused = NO;
663  [executor calculateNumFiles:[procFiles count]];
664}
665
666- (BOOL)connection:(NSConnection*)ancestor
667shouldMakeNewConnection:(NSConnection*)newConn
668{
669  if (ancestor == execconn)
670    {
671      [newConn setDelegate: self];
672      [nc addObserver: self
673	  selector: @selector(connectionDidDie:)
674	  name: NSConnectionDidDieNotification
675	  object: newConn];
676      return YES;
677    }
678
679  return NO;
680}
681
682- (void)connectionDidDie:(NSNotification *)notification
683{
684  [nc removeObserver: self
685	              name: NSConnectionDidDieNotification
686              object: [notification object]];
687
688  if (opdone == NO) {
689    NSRunAlertPanel(nil,
690                    NSLocalizedString(@"executor connection died!", @""),
691                    NSLocalizedString(@"Continue", @""),
692                    nil,
693                    nil);
694    [self sendDidChangeNotification];
695    [self endOperation];
696  }
697}
698
699- (NSString *)type
700{
701  return type;
702}
703
704- (NSString *)source
705{
706  return source;
707}
708
709- (NSString *)destination
710{
711  return destination;
712}
713
714- (NSArray *)files
715{
716  return files;
717}
718
719- (NSArray *)dupfiles
720{
721  return dupfiles;
722}
723
724- (int)ref
725{
726  return ref;
727}
728
729- (BOOL)showsWindow
730{
731  return showwin;
732}
733
734- (NSWindow *)win
735{
736  return win;
737}
738
739- (void) getWinRect: (NSRect*)rptr
740{
741  *rptr = NSZeroRect;
742  if (win && [win isVisible]) {
743    *rptr = [win frame];
744  }
745}
746
747@end
748
749
750@implementation FileOpExecutor
751
752+ (void)setPorts:(NSArray *)thePorts
753{
754  CREATE_AUTORELEASE_POOL(pool);
755  NSPort *port[2];
756  NSConnection *conn;
757  FileOpExecutor *executor;
758
759  port[0] = [thePorts objectAtIndex: 0];
760  port[1] = [thePorts objectAtIndex: 1];
761
762  conn = [NSConnection connectionWithReceivePort: (NSPort *)port[0]
763                                        sendPort: (NSPort *)port[1]];
764
765  executor = [[self alloc] init];
766  [executor setFileop: thePorts];
767  [(id)[conn rootProxy] registerExecutor: executor];
768  RELEASE (executor);
769
770  RELEASE (pool);
771}
772
773- (void)dealloc
774{
775  RELEASE (operation);
776  RELEASE (source);
777  RELEASE (destination);
778  RELEASE (files);
779  RELEASE (procfiles);
780  [super dealloc];
781}
782
783- (id)init
784{
785  self = [super init];
786
787  if (self) {
788    fm = [NSFileManager defaultManager];
789		samename = NO;
790    onlyolder = NO;
791  }
792
793  return self;
794}
795
796- (void)setFileop:(NSArray *)thePorts
797{
798  NSPort *port[2];
799  NSConnection *conn;
800  id anObject;
801
802  port[0] = [thePorts objectAtIndex: 0];
803  port[1] = [thePorts objectAtIndex: 1];
804
805  conn = [NSConnection connectionWithReceivePort: (NSPort *)port[0]
806                                        sendPort: (NSPort *)port[1]];
807
808  anObject = (id)[conn rootProxy];
809  [anObject setProtocolForProxy: @protocol(FileOpInfoProtocol)];
810  fileOp = (id <FileOpInfoProtocol>)anObject;
811}
812
813- (BOOL)setOperation:(NSData *)opinfo
814{
815  NSDictionary *opDict = [NSUnarchiver unarchiveObjectWithData: opinfo];
816  id dictEntry;
817
818  dictEntry = [opDict objectForKey: @"operation"];
819  if (dictEntry) {
820    ASSIGN (operation, dictEntry);
821  }
822
823  dictEntry = [opDict objectForKey: @"source"];
824  if (dictEntry) {
825    ASSIGN (source, dictEntry);
826  }
827
828  dictEntry = [opDict objectForKey: @"destination"];
829  if (dictEntry) {
830    ASSIGN (destination, dictEntry);
831  }
832
833  files = [NSMutableArray new];
834  dictEntry = [opDict objectForKey: @"files"];
835  if (dictEntry) {
836    [files addObjectsFromArray: dictEntry];
837  }
838
839  procfiles = [NSMutableArray new];
840
841  return YES;
842}
843
844- (BOOL)checkSameName
845{
846  NSArray *dirContents;
847  NSUInteger i;
848
849	samename = NO;
850
851  if (([operation isEqual: @"GWorkspaceRenameOperation"])
852        || ([operation isEqual: @"GWorkspaceCreateDirOperation"])
853        || ([operation isEqual: @"GWorkspaceCreateFileOperation"])) {
854    /* already checked by GWorkspace */
855	  return NO;
856  }
857
858  if (destination && [files count])
859    {
860      dirContents = [fm directoryContentsAtPath: destination];
861      for (i = 0; i < [files count]; i++)
862        {
863          NSDictionary *dict = [files objectAtIndex: i];
864          NSString *name = [dict objectForKey: @"name"];
865
866          if ([dirContents containsObject: name])
867            {
868              samename = YES;
869              break;
870            }
871        }
872    }
873
874  if (samename)
875    {
876      if (([operation isEqual: NSWorkspaceMoveOperation])
877          || ([operation isEqual: NSWorkspaceCopyOperation])
878          || ([operation isEqual: NSWorkspaceLinkOperation])
879          || ([operation isEqual: @"GWorkspaceRecycleOutOperation"]))
880        {
881          return YES;
882
883        }
884      else if (([operation isEqual: NSWorkspaceDestroyOperation])
885               || ([operation isEqual: NSWorkspaceDuplicateOperation])
886               || ([operation isEqual: NSWorkspaceRecycleOperation])
887               || ([operation isEqual: @"GWorkspaceEmptyRecyclerOperation"]))
888        {
889          return NO;
890        }
891    }
892
893  return NO;
894}
895
896- (void)setOnlyOlder:(BOOL)flag
897{
898  onlyolder = flag;
899}
900
901- (oneway void)calculateNumFiles:(NSUInteger)continueFrom
902{
903  NSUInteger i;
904  NSUInteger fnum = 0;
905
906  if (continueFrom == 0)
907    {
908      for (i = 0; i < [files count]; i++)
909        {
910          CREATE_AUTORELEASE_POOL (arp);
911          NSDictionary *dict = [files objectAtIndex: i];
912          NSString *name = [dict objectForKey: @"name"];
913          NSString *path = [source stringByAppendingPathComponent: name];
914          BOOL isDir = NO;
915
916          [fm fileExistsAtPath: path isDirectory: &isDir];
917
918          if (isDir)
919            {
920              NSDirectoryEnumerator *enumerator = [fm enumeratorAtPath: path];
921
922              while (1)
923                {
924                  CREATE_AUTORELEASE_POOL (arp2);
925                  NSString *dirEntry = [enumerator nextObject];
926
927                  if (dirEntry)
928                    {
929                      if (stopped)
930                        {
931                          RELEASE (arp2);
932                          break;
933                        }
934                      fnum++;
935                    }
936                  else
937                    {
938                      RELEASE (arp2);
939                      break;
940                    }
941                  RELEASE (arp2);
942                }
943            }
944          else
945            {
946              fnum++;
947            }
948
949          if (stopped)
950            {
951              RELEASE (arp);
952              break;
953            }
954          RELEASE (arp);
955        }
956
957      if (stopped)
958        {
959          [fileOp endOperation];
960          [fileOp cleanUpExecutor];
961        }
962
963      fcount = 0;
964      stepcount = 0;
965
966      if (fnum < PROGR_STEPS)
967        {
968          progstep = 1.0;
969        }
970      else
971        {
972          progstep = fnum / PROGR_STEPS;
973        }
974      [fileOp setNumFiles: fnum];
975    }
976  else
977    {
978      fcount = continueFrom;
979      stepcount = continueFrom;
980    }
981  [self performOperation];
982}
983
984- (oneway void)performOperation
985{
986  canupdate = YES;
987
988  if ([operation isEqual: NSWorkspaceMoveOperation]
989      || [operation isEqual: @"GWorkspaceRecycleOutOperation"])
990    {
991      [self doMove];
992    }
993  else if ([operation isEqual: NSWorkspaceCopyOperation])
994    {
995      [self doCopy];
996    }
997  else if ([operation isEqual: NSWorkspaceLinkOperation])
998    {
999      [self doLink];
1000    }
1001  else if ([operation isEqual: NSWorkspaceDestroyOperation]
1002	   || [operation isEqual: @"GWorkspaceEmptyRecyclerOperation"])
1003    {
1004      [self doRemove];
1005    }
1006  else if ([operation isEqual: NSWorkspaceDuplicateOperation])
1007    {
1008      [self doDuplicate];
1009    }
1010  else if ([operation isEqual: NSWorkspaceRecycleOperation])
1011    {
1012      [self doTrash];
1013    }
1014  else if ([operation isEqual: @"GWorkspaceRenameOperation"])
1015    {
1016      [self doRename];
1017    }
1018  else if ([operation isEqual: @"GWorkspaceCreateDirOperation"])
1019    {
1020      [self doNewFolder];
1021    }
1022  else if ([operation isEqual: @"GWorkspaceCreateFileOperation"])
1023    {
1024      [self doNewFile];
1025    }
1026}
1027
1028- (NSData *)processedFiles
1029{
1030  return [NSArchiver archivedDataWithRootObject: procfiles];
1031}
1032
1033#define CHECK_DONE \
1034if (([files count] == 0) || stopped || paused) break
1035
1036#define GET_FILENAME \
1037fileinfo = [files objectAtIndex: 0]; \
1038RETAIN (fileinfo); \
1039filename = [fileinfo objectForKey: @"name"];
1040
1041- (void)doMove
1042{
1043  while (1)
1044    {
1045      CHECK_DONE;
1046      GET_FILENAME;
1047
1048      if ((samename == NO) || (samename && [self removeExisting: fileinfo]))
1049	{
1050	  NSString *src = [source stringByAppendingPathComponent: filename];
1051	  NSString *dst = [destination stringByAppendingPathComponent: filename];
1052
1053	  if ([fm movePath: src toPath: dst handler: self])
1054	    {
1055	      [procfiles addObject: filename];
1056	    }
1057	  else
1058	    {
1059	      /* check for broken symlink */
1060	      NSDictionary *attributes = [fm fileAttributesAtPath: src traverseLink: NO];
1061
1062	      if (attributes && ([attributes fileType] == NSFileTypeSymbolicLink) && ([fm fileExistsAtPath: src] == NO))
1063		{
1064		  if ([fm copyPath: src toPath: dst handler: self] && [fm removeFileAtPath: src handler: self])
1065		    {
1066		      [procfiles addObject: filename];
1067		    }
1068		}
1069	    }
1070	}
1071
1072      [files removeObject: fileinfo];
1073      RELEASE (fileinfo);
1074    }
1075
1076  [fileOp sendDidChangeNotification];
1077  if (([files count] == 0) || stopped)
1078    {
1079      [fileOp endOperation];
1080    }
1081  else if (paused)
1082    {
1083      [fileOp removeProcessedFiles];
1084    }
1085  [fileOp cleanUpExecutor];
1086}
1087
1088- (void)doCopy
1089{
1090  while (1)
1091    {
1092      CHECK_DONE;
1093      GET_FILENAME;
1094
1095      if ((samename == NO) || (samename && [self removeExisting: fileinfo]))
1096        {
1097          if ([fm copyPath: [source stringByAppendingPathComponent: filename]
1098                    toPath: [destination stringByAppendingPathComponent: filename]
1099                   handler: self])
1100            {
1101              [procfiles addObject: filename];
1102            }
1103        }
1104      [files removeObject: fileinfo];
1105      RELEASE (fileinfo);
1106    }
1107
1108  [fileOp sendDidChangeNotification];
1109  if (([files count] == 0) || stopped)
1110    {
1111      [fileOp endOperation];
1112    }
1113  else if (paused)
1114    {
1115      [fileOp removeProcessedFiles];
1116    }
1117  [fileOp cleanUpExecutor];
1118}
1119
1120- (void)doLink
1121{
1122  while (1)
1123    {
1124      CHECK_DONE;
1125      GET_FILENAME;
1126
1127      if ((samename == NO) || (samename && [self removeExisting: fileinfo]))
1128	{
1129	  NSString *dst = [destination stringByAppendingPathComponent: filename];
1130	  NSString *src = [source stringByAppendingPathComponent: filename];
1131
1132	  if ([fm createSymbolicLinkAtPath: dst pathContent: src])
1133	    {
1134	      [procfiles addObject: filename];
1135	    }
1136	}
1137      [files removeObject: fileinfo];
1138      RELEASE (fileinfo);
1139    }
1140
1141  [fileOp sendDidChangeNotification];
1142  if (([files count] == 0) || stopped)
1143    {
1144      [fileOp endOperation];
1145    }
1146  else if (paused)
1147    {
1148      [fileOp removeProcessedFiles];
1149    }
1150  [fileOp cleanUpExecutor];
1151}
1152
1153- (void)doRemove
1154{
1155  while (1)
1156    {
1157      CHECK_DONE;
1158      GET_FILENAME;
1159
1160      if ([fm removeFileAtPath: [source stringByAppendingPathComponent: filename]
1161		       handler: self])
1162	{
1163	  [procfiles addObject: filename];
1164	}
1165      [files removeObject: fileinfo];
1166      RELEASE (fileinfo);
1167    }
1168
1169  [fileOp sendDidChangeNotification];
1170  if (([files count] == 0) || stopped)
1171    {
1172      [fileOp endOperation];
1173    }
1174  else if (paused)
1175    {
1176      [fileOp removeProcessedFiles];
1177    }
1178  [fileOp cleanUpExecutor];
1179}
1180
1181- (void)doDuplicate
1182{
1183  NSString *copystr = NSLocalizedString(@"_copy", @"");
1184  NSString *base;
1185  NSString *ext;
1186  NSString *destpath;
1187  NSString *newname;
1188  NSString *ntmp;
1189
1190  while (1) {
1191    int count = 1;
1192
1193	  CHECK_DONE;
1194	  GET_FILENAME;
1195
1196	  newname = [NSString stringWithString: filename];
1197    ext = [newname pathExtension];
1198    base = [newname stringByDeletingPathExtension];
1199
1200	  while (1) {
1201      if (count == 1) {
1202        ntmp = [NSString stringWithFormat: @"%@%@", base, copystr];
1203        if ([ext length]) {
1204          ntmp = [ntmp stringByAppendingPathExtension: ext];
1205        }
1206      } else {
1207        ntmp = [NSString stringWithFormat: @"%@%@%i", base, copystr, count];
1208        if ([ext length]) {
1209          ntmp = [ntmp stringByAppendingPathExtension: ext];
1210        }
1211      }
1212
1213		  destpath = [destination stringByAppendingPathComponent: ntmp];
1214
1215		  if ([fm fileExistsAtPath: destpath] == NO) {
1216        newname = ntmp;
1217			  break;
1218      } else {
1219        count++;
1220      }
1221	  }
1222
1223	  if ([fm copyPath: [destination stringByAppendingPathComponent: filename]
1224				      toPath: destpath
1225			       handler: self]) {
1226      [procfiles addObject: newname];
1227    }
1228	  [files removeObject: fileinfo];
1229    RELEASE (fileinfo);
1230  }
1231
1232  [fileOp sendDidChangeNotification];
1233  if (([files count] == 0) || stopped)
1234    {
1235      [fileOp endOperation];
1236    }
1237  else if (paused)
1238    {
1239      [fileOp removeProcessedFiles];
1240    }
1241  [fileOp cleanUpExecutor];
1242}
1243
1244- (void)doRename
1245{
1246  GET_FILENAME;
1247
1248  if ([fm movePath: source toPath: destination handler: self])
1249    {
1250      [procfiles addObject: filename];
1251
1252    }
1253  else
1254    {
1255      /* check for broken symlink */
1256      NSDictionary *attributes = [fm fileAttributesAtPath: source traverseLink: NO];
1257
1258      if (attributes && ([attributes fileType] == NSFileTypeSymbolicLink)
1259	  && ([fm fileExistsAtPath: source] == NO)) {
1260	if ([fm copyPath: source toPath: destination handler: self]
1261	    && [fm removeFileAtPath: source handler: self]) {
1262	  [procfiles addObject: filename];
1263	}
1264      }
1265    }
1266
1267  [files removeObject: fileinfo];
1268  RELEASE (fileinfo);
1269
1270  [fileOp sendDidChangeNotification];
1271  [fileOp endOperation];
1272  [fileOp cleanUpExecutor];
1273}
1274
1275- (void)doNewFolder
1276{
1277  GET_FILENAME;
1278
1279  if ([fm createDirectoryAtPath: [destination stringByAppendingPathComponent: filename]
1280		     attributes: nil]) {
1281    [procfiles addObject: filename];
1282  }
1283  [files removeObject: fileinfo];
1284  RELEASE (fileinfo);
1285
1286  [fileOp sendDidChangeNotification];
1287  [fileOp endOperation];
1288  [fileOp cleanUpExecutor];
1289}
1290
1291- (void)doNewFile
1292{
1293  GET_FILENAME;
1294
1295  if ([fm createFileAtPath: [destination stringByAppendingPathComponent: filename]
1296		  contents: nil
1297                attributes: nil]) {
1298    [procfiles addObject: filename];
1299  }
1300  [files removeObject: fileinfo];
1301  RELEASE (fileinfo);
1302
1303  [fileOp sendDidChangeNotification];
1304  [fileOp endOperation];
1305  [fileOp cleanUpExecutor];
1306}
1307
1308- (void)doTrash
1309{
1310  NSString *copystr = NSLocalizedString(@"_copy", @"");
1311  NSString *srcpath;
1312  NSString *destpath;
1313  NSString *newname;
1314  NSString *ntmp;
1315
1316  while (1)
1317    {
1318      CHECK_DONE;
1319      GET_FILENAME;
1320
1321    newname = [NSString stringWithString: filename];
1322    srcpath = [source stringByAppendingPathComponent: filename];
1323    destpath = [destination stringByAppendingPathComponent: newname];
1324
1325    if ([fm fileExistsAtPath: destpath]) {
1326      NSString *ext = [filename pathExtension];
1327      NSString *base = [filename stringByDeletingPathExtension];
1328      NSUInteger count = 1;
1329
1330	    while (1) {
1331        if (count == 1) {
1332          ntmp = [NSString stringWithFormat: @"%@%@", base, copystr];
1333          if ([ext length]) {
1334            ntmp = [ntmp stringByAppendingPathExtension: ext];
1335          }
1336        } else {
1337          ntmp = [NSString stringWithFormat: @"%@%@%lu", base, copystr, (unsigned long)count];
1338          if ([ext length]) {
1339            ntmp = [ntmp stringByAppendingPathExtension: ext];
1340          }
1341        }
1342
1343		    destpath = [destination stringByAppendingPathComponent: ntmp];
1344
1345		    if ([fm fileExistsAtPath: destpath] == NO) {
1346          newname = ntmp;
1347			    break;
1348        } else {
1349          count++;
1350        }
1351	    }
1352    }
1353
1354	  if ([fm movePath: srcpath toPath: destpath handler: self]) {
1355      [procfiles addObject: newname];
1356
1357    } else {
1358      /* check for broken symlink */
1359      NSDictionary *attributes = [fm fileAttributesAtPath: srcpath traverseLink: NO];
1360
1361      if (attributes && ([attributes fileType] == NSFileTypeSymbolicLink)
1362                                  && ([fm fileExistsAtPath: srcpath] == NO)) {
1363        if ([fm copyPath: srcpath toPath: destpath handler: self]
1364                          && [fm removeFileAtPath: srcpath handler: self]) {
1365          [procfiles addObject: newname];
1366        }
1367      }
1368    }
1369
1370	  [files removeObject: fileinfo];
1371    RELEASE (fileinfo);
1372  }
1373
1374  [fileOp sendDidChangeNotification];
1375  if (([files count] == 0) || stopped)
1376    {
1377      [fileOp endOperation];
1378    }
1379  else if (paused)
1380    {
1381      [fileOp removeProcessedFiles];
1382    }
1383  [fileOp cleanUpExecutor];
1384}
1385
1386- (BOOL)removeExisting:(NSDictionary *)info
1387{
1388  NSString *fname =  [info objectForKey: @"name"];
1389  NSString *destpath = [destination stringByAppendingPathComponent: fname];
1390  BOOL isdir;
1391
1392  canupdate = NO;
1393
1394  if ([fm fileExistsAtPath: destpath isDirectory: &isdir])
1395    {
1396      if (onlyolder)
1397	{
1398	  NSDictionary *attributes = [fm fileAttributesAtPath: destpath traverseLink: NO];
1399	  NSDate *dstdate = [attributes objectForKey: NSFileModificationDate];
1400	  NSDate *srcdate = [info objectForKey: @"date"];
1401
1402	  if ([srcdate isEqual: dstdate] == NO)
1403	    {
1404	      if ([[srcdate earlierDate: dstdate] isEqual: srcdate])
1405		{
1406		  canupdate = YES;
1407		  return NO;
1408		}
1409	    }
1410	  else
1411	    {
1412	      canupdate = YES;
1413	      return NO;
1414	    }
1415	}
1416
1417      [fm removeFileAtPath: destpath handler: self];
1418    }
1419
1420  canupdate = YES;
1421
1422  return YES;
1423}
1424
1425- (NSDictionary *)infoForFilename:(NSString *)name
1426{
1427  int i;
1428
1429  for (i = 0; i < [files count]; i++) {
1430    NSDictionary *info = [files objectAtIndex: i];
1431
1432    if ([[info objectForKey: @"name"] isEqual: name]) {
1433      return info;
1434    }
1435  }
1436
1437  return nil;
1438}
1439
1440
1441- (BOOL)fileManager:(NSFileManager *)manager
1442              shouldProceedAfterError:(NSDictionary *)errorDict
1443{
1444  NSString *path;
1445  NSString *error;
1446  NSString *msg;
1447  int result;
1448
1449  error = [errorDict objectForKey: @"Error"];
1450
1451  if ([error hasPrefix: @"Unable to change NSFileOwnerAccountID to to"]
1452        || [error hasPrefix: @"Unable to change NSFileOwnerAccountName to"]
1453        || [error hasPrefix: @"Unable to change NSFileGroupOwnerAccountID to"]
1454        || [error hasPrefix: @"Unable to change NSFileGroupOwnerAccountName to"]
1455        || [error hasPrefix: @"Unable to change NSFilePosixPermissions to"]
1456        || [error hasPrefix: @"Unable to change NSFileModificationDate to"]) {
1457    return YES;
1458  }
1459
1460  path = [NSString stringWithString: [errorDict objectForKey: @"NSFilePath"]];
1461
1462  msg = [NSString stringWithFormat: @"%@ %@\n%@ %@\n",
1463							NSLocalizedString(@"File operation error:", @""),
1464							error,
1465							NSLocalizedString(@"with file:", @""),
1466							path];
1467
1468  result = [fileOp requestUserConfirmationWithMessage: msg title: @"Error"];
1469
1470  if (result != NSAlertDefaultReturn)
1471    {
1472      [fileOp endOperation];
1473      [fileOp cleanUpExecutor];
1474    }
1475  else
1476    {
1477      BOOL found = NO;
1478
1479      while (1)
1480	{
1481	  NSDictionary *info = [self infoForFilename: [path lastPathComponent]];
1482
1483	  if ([path isEqual: source])
1484	    break;
1485
1486	  if (info)
1487	    {
1488	      [files removeObject: info];
1489	      found = YES;
1490	      break;
1491	    }
1492
1493	  path = [path stringByDeletingLastPathComponent];
1494	}
1495
1496    if ([files count])
1497      {
1498        if (found)
1499          {
1500            [self performOperation];
1501          }
1502        else
1503          {
1504            [fileOp showErrorAlertWithMessage: @"File Operation Error!"];
1505            [fileOp endOperation];
1506            [fileOp cleanUpExecutor];
1507          }
1508      }
1509    else
1510      {
1511        [fileOp endOperation];
1512        [fileOp cleanUpExecutor];
1513    }
1514  }
1515
1516  return YES;
1517}
1518
1519- (void)fileManager:(NSFileManager *)manager willProcessPath:(NSString *)path
1520{
1521  if (canupdate) {
1522    fcount++;
1523    stepcount++;
1524
1525    if (stepcount >= progstep) {
1526      stepcount = 0;
1527      [fileOp setProgIndicatorValue: fcount];
1528    }
1529  }
1530
1531  if (stopped)
1532    {
1533      [fileOp endOperation];
1534      [fileOp cleanUpExecutor];
1535    }
1536}
1537
1538@end
1539
1540