1/* 2 GNUstep ProjectCenter - http://www.gnustep.org/experience/ProjectCenter.html 3 4 Copyright (C) 2000-2011 Free Software Foundation 5 6 Authors: Philippe C.D. Robert 7 Serg Stoyan 8 9 This file is part of GNUstep. 10 11 This application is free software; you can redistribute it and/or 12 modify it under the terms of the GNU General Public 13 License as published by the Free Software Foundation; either 14 version 2 of the License, or (at your option) any later version. 15 16 This application 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 GNU 19 Library General Public License for more details. 20 21 You should have received a copy of the GNU General Public 22 License along with this library; if not, write to the Free 23 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. 24*/ 25 26#import <ProjectCenter/PCDefines.h> 27#import <ProjectCenter/PCFileManager.h> 28#import <ProjectCenter/PCFileCreator.h> 29#import <ProjectCenter/PCProjectManager.h> 30#import <ProjectCenter/PCProject.h> 31#import <ProjectCenter/PCProjectBrowser.h> 32#import <ProjectCenter/PCAddFilesPanel.h> 33 34#import <Protocols/Preferences.h> 35#import <ProjectCenter/PCLogController.h> 36 37@implementation PCFileManager 38 39// =========================================================================== 40// ==== Class methods 41// =========================================================================== 42 43static PCFileManager *_mgr = nil; 44 45+ (PCFileManager *)defaultManager 46{ 47 if (_mgr == nil) 48 { 49 _mgr = [[self alloc] init]; 50 } 51 52 return _mgr; 53} 54 55// =========================================================================== 56// ==== Init and free 57// =========================================================================== 58 59- (id)initWithProjectManager:(PCProjectManager *)aProjectManager 60{ 61 if ((self = [super init])) 62 { 63 projectManager = aProjectManager; 64 } 65 return self; 66} 67 68- (void)dealloc 69{ 70#ifdef DEVELOPMENT 71 NSLog (@"PCFileManager: dealloc"); 72#endif 73 74 if (addFilesPanel) 75 { 76 RELEASE(addFilesPanel); 77 } 78 79 [super dealloc]; 80} 81 82// =========================================================================== 83// ==== NSFileManager delegate methods 84// =========================================================================== 85- (BOOL) fileManager:(NSFileManager *)manager 86 shouldProceedAfterError:(NSDictionary *)errorDict 87{ 88 NSLog(@"FM error is: %@", [errorDict objectForKey:@"Error"]); 89 90 return YES; 91} 92 93// =========================================================================== 94// ==== File handling 95// =========================================================================== 96- (BOOL)createDirectoriesIfNeededAtPath:(NSString *)path 97{ 98 NSString *_path = [NSString stringWithString:path]; 99 NSString *_oldPath = nil; 100 NSMutableArray *pathArray = [NSMutableArray array]; 101 NSFileManager *fm = [NSFileManager defaultManager]; 102 BOOL isDir; 103 int i; 104 105 /* We stop when we find a file, or when we can't remove any path 106 * component any more. Else, you may end up in an infinite loop if 107 * _path = @"". 108 */ 109 isDir = NO; 110 while (_path != nil 111 && ![_path isEqualToString: _oldPath] 112 && ![fm fileExistsAtPath:_path isDirectory:&isDir]) 113 { 114 [pathArray addObject:[_path lastPathComponent]]; 115 _oldPath = _path; 116 _path = [_path stringByDeletingLastPathComponent]; 117 } 118 119 if (!isDir) 120 { 121 return NO; 122 } 123 124 if ([_path length] != [path length]) 125 { 126 for (i = [pathArray count]-1; i >= 0; i--) 127 { 128 _path = 129 [_path stringByAppendingPathComponent:[pathArray objectAtIndex:i]]; 130 if ([fm createDirectoryAtPath:_path attributes:nil] == NO) 131 { 132 return NO; 133 } 134 } 135 } 136 137 return YES; 138} 139 140- (BOOL)copyFile:(NSString *)file toFile:(NSString *)toFile 141{ 142 NSFileManager *fm = [NSFileManager defaultManager]; 143 NSString *directoryPath = nil; 144 145 if (!file) 146 { 147 return NO; 148 } 149 150 if (![fm fileExistsAtPath:toFile]) 151 { 152 directoryPath = [toFile stringByDeletingLastPathComponent]; 153 if ([self createDirectoriesIfNeededAtPath:directoryPath] == NO) 154 { 155 NSRunAlertPanel(@"Copy File", 156 @"Couldn't create directories at path %@", 157 @"Ok",nil,nil, directoryPath); 158 return NO; 159 } 160 161 if ([fm copyPath:file toPath:toFile handler:self] == NO) 162 { 163 NSRunAlertPanel(@"Copy File", 164 @"Couldn't copy file %@ to %@", 165 @"Ok",nil,nil, file, toFile); 166 return NO; 167 } 168 } 169 170 return YES; 171} 172 173- (BOOL)copyFile:(NSString *)file intoDirectory:(NSString *)directory 174{ 175 NSString *path = nil; 176 177 if (!file) 178 { 179 return NO; 180 } 181 182 path = [directory stringByAppendingPathComponent:[file lastPathComponent]]; 183 184 if (![self copyFile:file toFile:path]) 185 { // No need to open aler panel here 186 return NO; 187 } 188 189 return YES; 190} 191 192- (BOOL)copyFile:(NSString *)file 193 fromDirectory:(NSString *)fromDir 194 intoDirectory:(NSString *)toDir 195{ 196 NSString *path = nil; 197 198 if (!file || !fromDir || !toDir) 199 { 200 return NO; 201 } 202 203 path = [fromDir stringByAppendingPathComponent:[file lastPathComponent]]; 204 205 if (![self copyFile:path intoDirectory:toDir]) 206 { 207 return NO; 208 } 209 210 return YES; 211} 212 213- (BOOL)copyFiles:(NSArray *)files intoDirectory:(NSString *)directory 214{ 215 NSEnumerator *enumerator = nil; 216 NSString *file = nil; 217 218 if (!files) 219 { 220 return NO; 221 } 222 223 enumerator = [files objectEnumerator]; 224 while ((file = [enumerator nextObject])) 225 { 226 if ([self copyFile:file intoDirectory:directory] == NO) 227 { 228 return NO; 229 } 230 } 231 232 return YES; 233} 234 235- (BOOL)removeDirectoriesIfEmptyAtPath:(NSString *)path 236{ 237 NSFileManager *fm = [NSFileManager defaultManager]; 238 239 while ([[fm directoryContentsAtPath:path] count] == 0) 240 { 241 if ([fm removeFileAtPath:path handler:nil] == NO) 242 { 243 NSRunAlertPanel(@"Remove Directory", 244 @"Couldn't remove empty directory at path %@", 245 @"Ok",nil,nil, path); 246 return NO; 247 } 248 path = [path stringByDeletingLastPathComponent]; 249 } 250 251 return YES; 252} 253 254- (BOOL)removeFile:(NSString *)file 255 fromDirectory:(NSString *)directory 256 removeDirsIfEmpty:(BOOL)removeDirs 257{ 258 NSString *path = nil; 259 NSFileManager *fm = [NSFileManager defaultManager]; 260 261 if (!file) 262 { 263 return NO; 264 } 265 266 path = [directory stringByAppendingPathComponent:file]; 267 if (![fm removeFileAtPath:path handler:nil]) 268 { 269 NSRunAlertPanel(@"Remove File", 270 @"Couldn't remove file at path %@", 271 @"Ok",nil,nil, path); 272 return NO; 273 } 274 275 if (removeDirs) 276 { 277 [self removeDirectoriesIfEmptyAtPath:directory]; 278 } 279 280 return YES; 281} 282 283- (BOOL)removeFileAtPath:(NSString *)file removeDirsIfEmpty:(BOOL)removeDirs 284{ 285 return [self removeFile:[file lastPathComponent] 286 fromDirectory:[file stringByDeletingLastPathComponent] 287 removeDirsIfEmpty:removeDirs]; 288} 289 290- (BOOL)removeFiles:(NSArray *)files 291 fromDirectory:(NSString *)directory 292 removeDirsIfEmpty:(BOOL)removeDirs 293{ 294 NSEnumerator *filesEnum = nil; 295 NSString *file = nil; 296 297 if (!files) 298 { 299 return NO; 300 } 301 302 filesEnum = [files objectEnumerator]; 303 while ((file = [filesEnum nextObject])) 304 { 305 if ([self removeFile:file 306 fromDirectory:directory 307 removeDirsIfEmpty:removeDirs] == NO) 308 { 309 return NO; 310 } 311 } 312 313 return YES; 314} 315 316- (BOOL)moveFile:(NSString *)file intoDirectory:(NSString *)directory 317{ 318 if ([self copyFile:file intoDirectory:directory] == YES) 319 { 320 [self removeFileAtPath:file removeDirsIfEmpty:YES]; 321 } 322 else 323 { 324 NSRunAlertPanel(@"Move File", 325 @"Couldn't move file %@ to %@", 326 @"Ok",nil,nil, file, directory); 327 return NO; 328 } 329 330 return YES; 331} 332 333// =========================================================================== 334// ==== Find Executable 335// Tries to find the first matching executable tool fromt he given, nil-terminated 336// list. Returns the full path for it. 337// =========================================================================== 338- (NSString*) findExecutableToolFrom: (NSArray*)candidates 339{ 340 NSFileManager *manager; 341 NSEnumerator *pathEnumerator; 342 NSString *directory; 343 344 manager = [NSFileManager defaultManager]; 345 pathEnumerator = [NSSearchPathForDirectoriesInDomains(NSDeveloperDirectory, NSAllDomainsMask, YES) objectEnumerator]; 346 347 while (nil != (directory = [pathEnumerator nextObject])) 348 { 349 NSEnumerator *candidateEnumerator = [candidates objectEnumerator]; 350 NSString *candidate; 351 352 while (nil != (candidate = [candidateEnumerator nextObject])) 353 { 354 NSString *path = [directory stringByAppendingPathComponent: candidate]; 355 356 NSLog(@"final candidate path is: %@", path); 357 358 if ([manager isExecutableFileAtPath: path]) 359 { 360 return path; 361 } 362 } 363 } 364 return nil; 365} 366 367 368@end 369 370@implementation PCFileManager (UInterface) 371 372// =========================================================================== 373// ==== Panels 374// =========================================================================== 375 376- (id)_panelForOperation:(int)op 377 title:(NSString *)title 378 accView:(NSView *)accessoryView 379{ 380 id <PCPreferences> prefs = [projectManager prefController]; 381 NSString *lastOpenDir; 382 id panel; 383 384 operation = op; 385 386 switch (op) 387 { 388 case PCOpenFileOperation: 389 panel = [NSOpenPanel openPanel]; 390 [panel setCanChooseFiles:YES]; 391 [panel setCanChooseDirectories:NO]; 392 lastOpenDir = [prefs stringForKey:@"FileOpenLastDirectory"]; 393 break; 394 case PCSaveFileOperation: 395 panel = [NSSavePanel savePanel]; 396 lastOpenDir = [prefs stringForKey:@"FileSaveLastDirectory"]; 397 break; 398 case PCOpenProjectOperation: 399 panel = [NSOpenPanel openPanel]; 400 [panel setAllowsMultipleSelection:NO]; 401 [panel setCanChooseFiles:YES]; 402 [panel setCanChooseDirectories:YES]; 403 lastOpenDir = [prefs stringForKey:@"ProjectOpenLastDirectory"]; 404 break; 405 case PCOpenDirectoryOperation: 406 panel = [NSOpenPanel openPanel]; 407 [panel setCanChooseFiles:NO]; 408 [panel setCanChooseDirectories:YES]; 409 lastOpenDir = [prefs stringForKey:@"FileOpenLastDirectory"]; 410 break; 411 case PCAddFileOperation: 412 if (addFilesPanel == nil) 413 { 414 addFilesPanel = [PCAddFilesPanel addFilesPanel]; 415 [addFilesPanel setTreatsFilePackagesAsDirectories: YES]; 416 } 417 panel = addFilesPanel; 418 lastOpenDir = [prefs stringForKey:@"FileAddLastDirectory"]; 419 break; 420 default: 421 return nil; 422 break; 423 } 424 425 if (!lastOpenDir) 426 { 427 lastOpenDir = NSHomeDirectory(); 428 } 429 [panel setDirectory:lastOpenDir]; 430 [panel setDelegate:self]; 431 432 if (title != nil) 433 { 434 [panel setTitle:title]; 435 } 436 if (accessoryView != nil) 437 { 438 [panel setAccessoryView:accessoryView]; 439 } 440 441 442 return panel; 443} 444 445- (void)_saveLastDirectoryForPanel:(id)panel 446{ 447 id <PCPreferences> prefs = [projectManager prefController]; 448 NSString *key = nil; 449 450 switch (operation) 451 { 452 case PCOpenFileOperation: 453 key = @"FileOpenLastDirectory"; 454 break; 455 case PCSaveFileOperation: 456 key = @"FileSaveLastDirectory"; 457 break; 458 case PCOpenProjectOperation: 459 key = @"ProjectOpenLastDirectory"; 460 break; 461 case PCAddFileOperation: 462 key = @"FileAddLastDirectory"; 463 break; 464 default: 465 break; 466 } 467 468 if (key != nil) 469 { 470 [prefs setString:[panel directory] forKey:key notify:NO]; 471 } 472} 473 474- (NSMutableArray *)filesOfTypes:(NSArray *)types 475 operation:(int)op 476 multiple:(BOOL)yn 477 title:(NSString *)title 478 accView:(NSView *)accessoryView 479{ 480 id panel; 481 NSMutableArray *fileList = [[NSMutableArray alloc] init]; 482 int result = -10; 483 484 panel = [self _panelForOperation:op title:title accView:accessoryView]; 485 if (types != nil) 486 { 487 [panel setAllowedFileTypes:types]; 488 } 489 490 if ((op == PCOpenFileOperation) || 491 (op == PCOpenProjectOperation) || 492 (op == PCOpenDirectoryOperation)) 493 { 494 if ((result = [panel runModalForTypes:types]) == NSOKButton) 495 { 496 [fileList addObjectsFromArray:[panel filenames]]; 497 } 498 } 499 else if (op == PCSaveFileOperation) 500 { 501 if ((result = [panel runModal]) == NSOKButton) 502 { 503 [fileList addObject:[panel filename]]; 504 } 505 } 506 else if (op == PCAddFileOperation) 507 { 508 PCProject *project = [projectManager activeProject]; 509 NSString *selectedCategory = nil; 510 511 [panel setCategories:[project rootCategories]]; 512 selectedCategory = [[project projectBrowser] nameOfSelectedCategory]; 513 [panel selectCategory:selectedCategory]; 514 515 if ((result = [panel runModalForTypes:types]) == NSOKButton) 516 { 517 [fileList addObjectsFromArray:[panel filenames]]; 518 } 519 } 520 521 if (result == NSOKButton) 522 { 523 [self _saveLastDirectoryForPanel:panel]; 524 return [fileList autorelease]; 525 } 526 527 return nil; 528} 529 530// ============================================================================ 531// ==== PCAddFilesPanel delegate 532// ============================================================================ 533 534- (void)categoryChangedTo:(NSString *)category 535{ 536 PCProject *project = [projectManager activeProject]; 537 NSArray *fileTypes = nil; 538 PCProjectBrowser *browser = [project projectBrowser]; 539 NSString *path = [browser path]; 540 541 [addFilesPanel setTitle:[NSString stringWithFormat:@"Add %@",category]]; 542 543 fileTypes = [project 544 fileTypesForCategoryKey:[project keyForCategory:category]]; 545 [addFilesPanel setFileTypes:fileTypes]; 546 547 // Set project browser path 548 path = [path stringByDeletingLastPathComponent]; 549 path = [path stringByAppendingPathComponent:category]; 550 [browser setPath:path]; 551} 552 553// ============================================================================ 554// ==== NSOpenPanel and NSSavePanel delegate 555// ============================================================================ 556 557// If file name already in project -- don't show it! 558- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename 559{ 560 NSFileManager *fileManager = [NSFileManager defaultManager]; 561 BOOL isDir; 562 PCProject *project = nil; 563 NSArray *fileTypes = nil; 564 NSString *category = nil; 565 NSString *categoryKey = nil; 566 567 [fileManager fileExistsAtPath:filename isDirectory:&isDir]; 568 569 if ([[filename pathExtension] isEqualToString:@"gorm"]) 570 { 571 isDir = NO; 572 } 573 574 if (sender == addFilesPanel && !isDir) 575 { 576 project = [projectManager activeProject]; 577 category = [addFilesPanel selectedCategory]; 578 categoryKey = [project keyForCategory:category]; 579 fileTypes = [project fileTypesForCategoryKey:categoryKey]; 580 // Wrong file extension 581 if (fileTypes 582 && ![fileTypes containsObject:[filename pathExtension]]) 583 { 584 return NO; 585 } 586 // File is already in project 587 if (![project doesAcceptFile:filename forKey:categoryKey]) 588 { 589 return NO; 590 } 591 } 592 593 return YES; 594} 595 596// Test if we should accept file name selected or entered 597- (BOOL)panel:(id)sender isValidFilename:(NSString *)filename 598{ 599 NSFileManager *fm = [NSFileManager defaultManager]; 600 BOOL isDir; 601 NSEnumerator *e = nil; 602 NSArray *tempList = nil; 603 NSString *tempExtension = nil; 604 605 if (operation == PCOpenProjectOperation) 606 { 607 if ([fm fileExistsAtPath:filename isDirectory:&isDir] && isDir) 608 { 609 e = [[sender allowedFileTypes] objectEnumerator]; 610 while ((tempExtension = [e nextObject]) != nil) 611 { 612 tempList = [self filesWithExtension:tempExtension 613 atPath:filename 614 includeDirs:YES]; 615 if ([tempList count] > 0) 616 { 617 return YES; 618 } 619 } 620 621 return NO; 622 } 623 } 624 625 return YES; 626} 627 628@end 629 630@implementation PCFileManager (Misc) 631 632/** 633 * Returns YES if the file identified by `filename' is a text file, 634 * otherwise returns NO. 635 * 636 * The test is one by reading the first 512 bytes of the file 637 * and checking whether at least 90% of the data are printable 638 * ASCII characters. 639 * 640 * Author Saso Kiselkov 641 */ 642- (BOOL)isTextFile:(NSString *)filename 643{ 644 NSFileHandle *fh; 645 NSData *data; 646 NSUInteger i, printable = 0; 647 NSString *content; 648 NSCharacterSet *alpha = [NSCharacterSet alphanumericCharacterSet]; 649 NSCharacterSet *spaces = [NSCharacterSet whitespaceAndNewlineCharacterSet]; 650 NSCharacterSet *marks = [NSCharacterSet punctuationCharacterSet]; 651 652 fh = [NSFileHandle fileHandleForReadingAtPath:filename]; 653 if (fh == nil) 654 { 655 return NO; 656 } 657 658 data = [fh readDataOfLength:512]; 659 if ([data length] == 0) 660 { 661 return YES; 662 } 663 664 content = [NSString stringWithContentsOfFile: filename]; 665 for (i = 0; i < [content length]; i++) 666 { 667 if ([alpha characterIsMember: [content characterAtIndex: i]] || 668 [spaces characterIsMember: [content characterAtIndex: i]] || 669 [marks characterIsMember: [content characterAtIndex: i]]) 670 { 671 printable++; 672 } 673 } 674 675 return (((double) printable / i) > 0.9); 676} 677 678- (NSArray *)filesWithExtension:(NSString *)extension 679 atPath:(NSString *)dirPath 680 includeDirs:(BOOL)incDirs 681{ 682 NSFileManager *fm = [NSFileManager defaultManager]; 683 NSMutableArray *filesList = [[NSMutableArray alloc] init]; 684 NSEnumerator *e = nil; 685 NSString *temp = nil; 686 BOOL isDir; 687 688 e = [[fm directoryContentsAtPath:dirPath] objectEnumerator]; 689 while ((temp = [e nextObject]) != nil) 690 { 691 if ([fm fileExistsAtPath:temp isDirectory:&isDir] && isDir && !incDirs) 692 { 693 continue; 694 } 695 696 if ([[temp pathExtension] isEqual:extension]) 697 { 698 [filesList addObject:[dirPath stringByAppendingPathComponent:temp]]; 699 } 700 } 701 702 return [filesList autorelease]; 703} 704 705@end 706