1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 22 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN 23 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered 30 trademarks of Be Incorporated in the United States and other countries. Other 31 brand product names are registered trademarks or trademarks of their 32 respective holders. All rights reserved. 33 */ 34 35 // Tracker file system calls. 36 37 // APIs/code in FSUtils.h and FSUtils.cpp is slated for a major cleanup -- in 38 // other words, you will find a lot of ugly cruft in here 39 40 // ToDo: 41 // Move most of preflight error checks to the Model level and only keep those 42 // that have to do with size, reading/writing and name collisions. 43 // Get rid of all the BList based APIs, use BObjectLists. 44 // Clean up the error handling, push most of the user interaction out of the 45 // low level FS calls. 46 47 48 #include <ctype.h> 49 #include <errno.h> 50 #include <strings.h> 51 #include <unistd.h> 52 53 #include <Alert.h> 54 #include <Application.h> 55 #include <Catalog.h> 56 #include <Debug.h> 57 #include <Directory.h> 58 #include <Entry.h> 59 #include <FindDirectory.h> 60 #include <Locale.h> 61 #include <NodeInfo.h> 62 #include <Path.h> 63 #include <Roster.h> 64 #include <Screen.h> 65 #include <String.h> 66 #include <StringFormat.h> 67 #include <SymLink.h> 68 #include <Volume.h> 69 #include <VolumeRoster.h> 70 71 #include <fs_attr.h> 72 #include <fs_info.h> 73 #include <sys/utsname.h> 74 75 #include <AutoLocker.h> 76 #include <libroot/libroot_private.h> 77 #include <system/syscalls.h> 78 79 #include "Attributes.h" 80 #include "Bitmaps.h" 81 #include "Commands.h" 82 #include "FSUndoRedo.h" 83 #include "FSUtils.h" 84 #include "InfoWindow.h" 85 #include "MimeTypes.h" 86 #include "OverrideAlert.h" 87 #include "StatusWindow.h" 88 #include "Thread.h" 89 #include "Tracker.h" 90 #include "TrackerSettings.h" 91 #include "Utilities.h" 92 #include "VirtualDirectoryManager.h" 93 94 95 enum { 96 kUserCanceled = B_ERRORS_END + 1, 97 kCopyCanceled = kUserCanceled, 98 kTrashCanceled 99 }; 100 101 enum ConflictCheckResult { 102 kCanceled = kUserCanceled, 103 kPrompt, 104 kSkipAll, 105 kReplace, 106 kReplaceAll, 107 kNoConflicts 108 }; 109 110 111 namespace BPrivate { 112 113 #undef B_TRANSLATION_CONTEXT 114 #define B_TRANSLATION_CONTEXT "FSUtils" 115 116 static status_t FSDeleteFolder(BEntry*, CopyLoopControl*, bool updateStatus, 117 bool deleteTopDir = true, bool upateFileNameInStatus = false); 118 static status_t MoveEntryToTrash(BEntry*, BPoint*, Undo &undo); 119 static void LowLevelCopy(BEntry*, StatStruct*, BDirectory*, char* destName, 120 CopyLoopControl*, BPoint*); 121 status_t DuplicateTask(BObjectList<entry_ref>* srcList); 122 static status_t MoveTask(BObjectList<entry_ref>*, BEntry*, BList*, uint32); 123 static status_t _DeleteTask(BObjectList<entry_ref>*, bool); 124 static status_t _RestoreTask(BObjectList<entry_ref>*); 125 status_t CalcItemsAndSize(CopyLoopControl* loopControl, 126 BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount, 127 off_t* totalSize); 128 status_t MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc, 129 uint32 moveMode, const char* newName, Undo &undo, 130 CopyLoopControl* loopControl); 131 ConflictCheckResult PreFlightNameCheck(BObjectList<entry_ref>* srcList, 132 const BDirectory* destDir, int32* collisionCount, uint32 moveMode); 133 status_t CheckName(uint32 moveMode, const BEntry* srcEntry, 134 const BDirectory* destDir, bool multipleCollisions, 135 ConflictCheckResult &); 136 void CopyAttributes(CopyLoopControl* control, BNode* srcNode, 137 BNode* destNode, void* buffer, size_t bufsize); 138 void CopyPoseLocation(BNode* src, BNode* dest); 139 bool DirectoryMatchesOrContains(const BEntry*, directory_which); 140 bool DirectoryMatchesOrContains(const BEntry*, const char* additionalPath, 141 directory_which); 142 bool DirectoryMatches(const BEntry*, directory_which); 143 bool DirectoryMatches(const BEntry*, const char* additionalPath, 144 directory_which); 145 146 status_t empty_trash(void*); 147 148 149 static const char* kDeleteConfirmationStr = 150 B_TRANSLATE_MARK("Are you sure you want to delete the " 151 "selected item(s)? This operation cannot be reverted."); 152 153 static const char* kReplaceStr = 154 B_TRANSLATE_MARK("You are trying to replace the item:\n" 155 "\t%name%dest\n" 156 "with:\n" 157 "\t%name%src\n\n" 158 "Would you like to replace it with the one you are %movemode?"); 159 160 static const char* kDirectoryReplaceStr = 161 B_TRANSLATE_MARK("An item named \"%name\" already exists in " 162 "this folder, and may contain\nitems with the same names. Would you like " 163 "to replace them with those contained in the folder you are %verb?"); 164 165 static const char* kSymLinkReplaceStr = 166 B_TRANSLATE_MARK("An item named \"%name\" already exists in this " 167 "folder. Would you like to replace it with the symbolic link you are " 168 "creating?"); 169 170 static const char* kNoFreeSpace = 171 B_TRANSLATE_MARK("Sorry, there is not enough free space on the " 172 "destination volume to copy the selection."); 173 174 static const char* kFileErrorString = 175 B_TRANSLATE_MARK("Error copying file \"%name\":\n\t%error\n\n" 176 "Would you like to continue?"); 177 178 static const char* kFolderErrorString = 179 B_TRANSLATE_MARK("Error copying folder \"%name\":\n\t%error\n\n" 180 "Would you like to continue?"); 181 182 static const char* kFileDeleteErrorString = 183 B_TRANSLATE_MARK("There was an error deleting \"%name\"" 184 ":\n\t%error"); 185 186 static const char* kReplaceManyStr = 187 B_TRANSLATE_MARK("Some items already exist in this folder with " 188 "the same names as the items you are %verb.\n \nWould you like to " 189 "replace them with the ones you are %verb or be prompted for each " 190 "one?"); 191 192 static const char* kFindAlternativeStr = 193 B_TRANSLATE_MARK("Would you like to find some other suitable " 194 "application?"); 195 196 static const char* kFindApplicationStr = 197 B_TRANSLATE_MARK("Would you like to find a suitable application " 198 "to open the file?"); 199 200 201 // Skip these attributes when copying in Tracker 202 const char* kSkipAttributes[] = { 203 kAttrPoseInfo, 204 NULL 205 }; 206 207 208 // #pragma mark - CopyLoopControl 209 210 211 CopyLoopControl::~CopyLoopControl() 212 { 213 } 214 215 216 void 217 CopyLoopControl::Init(uint32 jobKind) 218 { 219 } 220 221 222 void 223 CopyLoopControl::Init(int32 totalItems, off_t totalSize, 224 const entry_ref* destDir, bool showCount) 225 { 226 } 227 228 229 bool 230 CopyLoopControl::FileError(const char* message, const char* name, 231 status_t error, bool allowContinue) 232 { 233 return false; 234 } 235 236 237 void 238 CopyLoopControl::UpdateStatus(const char* name, const entry_ref& ref, 239 int32 count, bool optional) 240 { 241 } 242 243 244 bool 245 CopyLoopControl::CheckUserCanceled() 246 { 247 return false; 248 } 249 250 251 CopyLoopControl::OverwriteMode 252 CopyLoopControl::OverwriteOnConflict(const BEntry* srcEntry, 253 const char* destName, const BDirectory* destDir, bool srcIsDir, 254 bool dstIsDir) 255 { 256 return kReplace; 257 } 258 259 260 bool 261 CopyLoopControl::SkipEntry(const BEntry*, bool) 262 { 263 // Tracker makes no exceptions 264 return false; 265 } 266 267 268 void 269 CopyLoopControl::ChecksumChunk(const char*, size_t) 270 { 271 } 272 273 274 bool 275 CopyLoopControl::ChecksumFile(const entry_ref*) 276 { 277 return true; 278 } 279 280 281 bool 282 CopyLoopControl::SkipAttribute(const char*) 283 { 284 return false; 285 } 286 287 288 bool 289 CopyLoopControl::PreserveAttribute(const char*) 290 { 291 return false; 292 } 293 294 295 // #pragma mark - TrackerCopyLoopControl 296 297 298 TrackerCopyLoopControl::TrackerCopyLoopControl() 299 : 300 fThread(find_thread(NULL)), 301 fSourceList(NULL) 302 { 303 } 304 305 306 TrackerCopyLoopControl::TrackerCopyLoopControl(uint32 jobKind) 307 : 308 fThread(find_thread(NULL)), 309 fSourceList(NULL) 310 { 311 Init(jobKind); 312 } 313 314 315 TrackerCopyLoopControl::TrackerCopyLoopControl(int32 totalItems, 316 off_t totalSize) 317 : 318 fThread(find_thread(NULL)), 319 fSourceList(NULL) 320 { 321 Init(totalItems, totalSize); 322 } 323 324 325 TrackerCopyLoopControl::~TrackerCopyLoopControl() 326 { 327 if (gStatusWindow != NULL) 328 gStatusWindow->RemoveStatusItem(fThread); 329 } 330 331 332 void 333 TrackerCopyLoopControl::Init(uint32 jobKind) 334 { 335 if (gStatusWindow != NULL) 336 gStatusWindow->CreateStatusItem(fThread, (StatusWindowState)jobKind); 337 } 338 339 340 void 341 TrackerCopyLoopControl::Init(int32 totalItems, off_t totalSize, 342 const entry_ref* destDir, bool showCount) 343 { 344 if (gStatusWindow != NULL) { 345 gStatusWindow->InitStatusItem(fThread, totalItems, totalSize, 346 destDir, showCount); 347 } 348 } 349 350 351 bool 352 TrackerCopyLoopControl::FileError(const char* message, const char* name, 353 status_t error, bool allowContinue) 354 { 355 BString buffer(message); 356 buffer.ReplaceFirst("%name", name); 357 buffer.ReplaceFirst("%error", strerror(error)); 358 359 if (allowContinue) { 360 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"), 361 B_TRANSLATE("OK"), 0, B_WIDTH_AS_USUAL, B_STOP_ALERT); 362 alert->SetShortcut(0, B_ESCAPE); 363 return alert->Go() != 0; 364 } 365 366 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"), 0, 0, 367 B_WIDTH_AS_USUAL, B_STOP_ALERT); 368 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 369 alert->Go(); 370 return false; 371 } 372 373 374 void 375 TrackerCopyLoopControl::UpdateStatus(const char* name, const entry_ref&, 376 int32 count, bool optional) 377 { 378 if (gStatusWindow != NULL) 379 gStatusWindow->UpdateStatus(fThread, name, count, optional); 380 } 381 382 383 bool 384 TrackerCopyLoopControl::CheckUserCanceled() 385 { 386 if (gStatusWindow == NULL) 387 return false; 388 389 if (gStatusWindow->CheckCanceledOrPaused(fThread)) 390 return true; 391 392 if (fSourceList != NULL) { 393 // TODO: Check if the user dropped additional files onto this job. 394 // printf("%p->CheckUserCanceled()\n", this); 395 } 396 397 return false; 398 } 399 400 401 bool 402 TrackerCopyLoopControl::SkipAttribute(const char* attributeName) 403 { 404 for (const char** skipAttribute = kSkipAttributes; *skipAttribute; 405 skipAttribute++) { 406 if (strcmp(*skipAttribute, attributeName) == 0) 407 return true; 408 } 409 410 return false; 411 } 412 413 414 void 415 TrackerCopyLoopControl::SetSourceList(EntryList* list) 416 { 417 fSourceList = list; 418 } 419 420 421 // #pragma mark - the rest 422 423 424 static BNode* 425 GetWritableNode(BEntry* entry, StatStruct* statBuf = 0) 426 { 427 // utility call that works around the problem with BNodes not being 428 // universally writeable 429 // BNodes created on files will fail to WriteAttr because they do not 430 // have the right r/w permissions 431 432 StatStruct localStatbuf; 433 434 if (!statBuf) { 435 statBuf = &localStatbuf; 436 if (entry->GetStat(statBuf) != B_OK) 437 return 0; 438 } 439 440 if (S_ISREG(statBuf->st_mode)) 441 return new BFile(entry, O_RDWR); 442 443 return new BNode(entry); 444 } 445 446 447 bool 448 CheckDevicesEqual(const entry_ref* srcRef, const Model* targetModel) 449 { 450 BDirectory destDir (targetModel->EntryRef()); 451 struct stat deststat; 452 destDir.GetStat(&deststat); 453 454 return srcRef->device == deststat.st_dev; 455 } 456 457 458 status_t 459 FSSetPoseLocation(ino_t destDirInode, BNode* destNode, BPoint point) 460 { 461 PoseInfo poseInfo; 462 poseInfo.fInvisible = false; 463 poseInfo.fInitedDirectory = destDirInode; 464 poseInfo.fLocation = point; 465 466 status_t result = destNode->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, 467 &poseInfo, sizeof(poseInfo)); 468 469 if (result == sizeof(poseInfo)) 470 return B_OK; 471 472 return result; 473 } 474 475 476 status_t 477 FSSetPoseLocation(BEntry* entry, BPoint point) 478 { 479 BNode node(entry); 480 status_t result = node.InitCheck(); 481 if (result != B_OK) 482 return result; 483 484 BDirectory parent; 485 result = entry->GetParent(&parent); 486 if (result != B_OK) 487 return result; 488 489 node_ref destNodeRef; 490 result = parent.GetNodeRef(&destNodeRef); 491 if (result != B_OK) 492 return result; 493 494 return FSSetPoseLocation(destNodeRef.node, &node, point); 495 } 496 497 498 bool 499 FSGetPoseLocation(const BNode* node, BPoint* point) 500 { 501 PoseInfo poseInfo; 502 if (ReadAttr(node, kAttrPoseInfo, kAttrPoseInfoForeign, 503 B_RAW_TYPE, 0, &poseInfo, sizeof(poseInfo), &PoseInfo::EndianSwap) 504 == kReadAttrFailed) { 505 return false; 506 } 507 508 if (poseInfo.fInitedDirectory == -1LL) 509 return false; 510 511 *point = poseInfo.fLocation; 512 513 return true; 514 } 515 516 517 static void 518 SetUpPoseLocation(ino_t sourceParentIno, ino_t destParentIno, 519 const BNode* sourceNode, BNode* destNode, BPoint* loc) 520 { 521 BPoint point; 522 if (loc == NULL 523 // we don't have a position yet 524 && sourceParentIno != destParentIno 525 // we aren't copying into the same directory 526 && FSGetPoseLocation(sourceNode, &point)) { 527 // the original has a valid inited location 528 loc = &point; 529 // copy the originals location 530 } 531 532 if (loc != NULL && loc != (BPoint*)-1) { 533 // loc of -1 is used when copying/moving into a window in list mode 534 // where copying positions would not work 535 // ToSo: 536 // should push all this logic to upper levels 537 FSSetPoseLocation(destParentIno, destNode, *loc); 538 } 539 } 540 541 542 void 543 FSMoveToFolder(BObjectList<entry_ref>* srcList, BEntry* destEntry, 544 uint32 moveMode, BList* pointList) 545 { 546 if (srcList->IsEmpty()) { 547 delete srcList; 548 delete pointList; 549 delete destEntry; 550 return; 551 } 552 553 LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList, 554 destEntry, pointList, moveMode); 555 } 556 557 558 void 559 FSDelete(entry_ref* ref, bool async, bool confirm) 560 { 561 BObjectList<entry_ref>* list = new BObjectList<entry_ref>(1, true); 562 list->AddItem(ref); 563 FSDeleteRefList(list, async, confirm); 564 } 565 566 567 void 568 FSDeleteRefList(BObjectList<entry_ref>* list, bool async, bool confirm) 569 { 570 if (async) { 571 LaunchInNewThread("DeleteTask", B_NORMAL_PRIORITY, _DeleteTask, list, 572 confirm); 573 } else 574 _DeleteTask(list, confirm); 575 } 576 577 578 void 579 FSRestoreRefList(BObjectList<entry_ref>* list, bool async) 580 { 581 if (async) { 582 LaunchInNewThread("RestoreTask", B_NORMAL_PRIORITY, _RestoreTask, 583 list); 584 } else 585 _RestoreTask(list); 586 } 587 588 589 void 590 FSMoveToTrash(BObjectList<entry_ref>* srcList, BList* pointList, bool async) 591 { 592 if (srcList->IsEmpty()) { 593 delete srcList; 594 delete pointList; 595 return; 596 } 597 598 if (async) 599 LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList, 600 (BEntry*)0, pointList, kMoveSelectionTo); 601 else 602 MoveTask(srcList, 0, pointList, kMoveSelectionTo); 603 } 604 605 606 static bool 607 IsDisksWindowIcon(BEntry* entry) 608 { 609 BPath path; 610 if (entry->InitCheck() != B_OK || entry->GetPath(&path) != B_OK) 611 return false; 612 613 return strcmp(path.Path(), "/") == 0; 614 } 615 616 enum { 617 kNotConfirmed, 618 kConfirmedHomeMove, 619 kConfirmedAll 620 }; 621 622 623 bool 624 ConfirmChangeIfWellKnownDirectory(const BEntry* entry, DestructiveAction action, 625 bool dontAsk, int32* confirmedAlready) 626 { 627 // Don't let the user casually move/change important files/folders 628 // 629 // This is a cheap replacement for having a real UID support turned 630 // on and not running as root all the time 631 632 if (confirmedAlready && *confirmedAlready == kConfirmedAll) 633 return true; 634 635 if (FSIsDeskDir(entry) || FSIsTrashDir(entry) || FSIsRootDir(entry)) 636 return false; 637 638 if ((!DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY) 639 && !DirectoryMatchesOrContains(entry, B_USER_DIRECTORY)) 640 || DirectoryMatchesOrContains(entry, B_SYSTEM_TEMP_DIRECTORY)) 641 // quick way out 642 return true; 643 644 BString warning; 645 bool requireOverride = true; 646 647 if (DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY)) { 648 if (action == kRename) { 649 warning.SetTo( 650 B_TRANSLATE("If you rename the system folder or its " 651 "contents, you won't be able to boot %osName!\n\nAre you sure " 652 "you want to do this?\n\nTo rename the system folder or its " 653 "contents anyway, hold down the Shift key and click " 654 "\"Rename\".")); 655 } else if(action == kMove) { 656 warning.SetTo( 657 B_TRANSLATE("If you move the system folder or its " 658 "contents, you won't be able to boot %osName!\n\nAre you sure " 659 "you want to do this?\n\nTo move the system folder or its " 660 "contents anyway, hold down the Shift key and click " 661 "\"Move\".")); 662 } else { 663 warning.SetTo( 664 B_TRANSLATE("If you alter the system folder or its " 665 "contents, you won't be able to boot %osName!\n\nAre you sure " 666 "you want to do this?\n\nTo alter the system folder or its " 667 "contents anyway, hold down the Shift key and click " 668 "\"I know what I'm doing\".")); 669 } 670 } else if (DirectoryMatches(entry, B_USER_DIRECTORY)) { 671 if (action == kRename) { 672 warning .SetTo( 673 B_TRANSLATE("If you rename the home folder, %osName " 674 "may not behave properly!\n\nAre you sure you want to do this?" 675 "\n\nTo rename the home folder anyway, hold down the " 676 "Shift key and click \"Rename\".")); 677 } else if (action == kMove) { 678 warning .SetTo( 679 B_TRANSLATE("If you move the home folder, %osName " 680 "may not behave properly!\n\nAre you sure you want to do this?" 681 "\n\nTo move the home folder anyway, hold down the " 682 "Shift key and click \"Move\".")); 683 } else { 684 warning .SetTo( 685 B_TRANSLATE("If you alter the home folder, %osName " 686 "may not behave properly!\n\nAre you sure you want to do this?" 687 "\n\nTo alter the home folder anyway, hold down the " 688 "Shift key and click \"I know what I'm doing\".")); 689 } 690 } else if (DirectoryMatchesOrContains(entry, B_USER_CONFIG_DIRECTORY) 691 || DirectoryMatchesOrContains(entry, B_SYSTEM_SETTINGS_DIRECTORY)) { 692 if (action == kRename) { 693 warning.SetTo( 694 B_TRANSLATE("If you rename %target, %osName may not behave " 695 "properly!\n\nAre you sure you want to do this?")); 696 } else if (action == kMove) { 697 warning.SetTo( 698 B_TRANSLATE("If you move %target, %osName may not behave " 699 "properly!\n\nAre you sure you want to do this?")); 700 } else { 701 warning.SetTo( 702 B_TRANSLATE("If you alter %target, %osName may not behave " 703 "properly!\n\nAre you sure you want to do this?")); 704 } 705 706 if (DirectoryMatchesOrContains(entry, "beos_mime", 707 B_USER_SETTINGS_DIRECTORY) 708 || DirectoryMatchesOrContains(entry, "beos_mime", 709 B_SYSTEM_SETTINGS_DIRECTORY)) { 710 warning.ReplaceFirst("%target", B_TRANSLATE("the MIME settings")); 711 requireOverride = false; 712 } else if (DirectoryMatches(entry, B_USER_CONFIG_DIRECTORY)) { 713 warning.ReplaceFirst("%target", B_TRANSLATE("the config folder")); 714 requireOverride = false; 715 } else if (DirectoryMatches(entry, B_USER_SETTINGS_DIRECTORY) 716 || DirectoryMatches(entry, B_SYSTEM_SETTINGS_DIRECTORY)) { 717 warning.ReplaceFirst("%target", B_TRANSLATE("the settings folder")); 718 requireOverride = false; 719 } else { 720 // It was not a special directory/file after all. Allow renaming. 721 return true; 722 } 723 } else 724 return true; 725 726 if (dontAsk) 727 return false; 728 729 if (confirmedAlready && *confirmedAlready == kConfirmedHomeMove 730 && !requireOverride) 731 // we already warned about moving home this time around 732 return true; 733 734 struct utsname name; 735 if (uname(&name) == -1) 736 warning.ReplaceFirst("%osName", "Haiku"); 737 else 738 warning.ReplaceFirst("%osName", name.sysname); 739 740 BString buttonLabel; 741 if (action == kRename) { 742 buttonLabel = B_TRANSLATE_COMMENT("Rename", "button label"); 743 } else if (action == kMove) { 744 buttonLabel = B_TRANSLATE_COMMENT("Move", "button label"); 745 } else { 746 buttonLabel = B_TRANSLATE_COMMENT("I know what I'm doing", 747 "button label"); 748 } 749 750 OverrideAlert* alert = new OverrideAlert("", warning.String(), 751 buttonLabel.String(), (requireOverride ? B_SHIFT_KEY : 0), 752 B_TRANSLATE("Cancel"), 0, NULL, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 753 alert->SetShortcut(1, B_ESCAPE); 754 if (alert->Go() == 1) { 755 if (confirmedAlready) 756 *confirmedAlready = kNotConfirmed; 757 return false; 758 } 759 760 if (confirmedAlready) { 761 if (!requireOverride) 762 *confirmedAlready = kConfirmedHomeMove; 763 else 764 *confirmedAlready = kConfirmedAll; 765 } 766 767 return true; 768 } 769 770 771 static status_t 772 InitCopy(CopyLoopControl* loopControl, uint32 moveMode, 773 BObjectList<entry_ref>* srcList, BVolume* dstVol, BDirectory* destDir, 774 entry_ref* destRef, bool preflightNameCheck, bool needSizeCalculation, 775 int32* collisionCount, ConflictCheckResult* preflightResult) 776 { 777 if (dstVol->IsReadOnly()) { 778 BAlert* alert = new BAlert("", 779 B_TRANSLATE("You can't move or copy items to read-only volumes."), 780 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 781 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 782 alert->Go(); 783 return B_ERROR; 784 } 785 786 int32 numItems = srcList->CountItems(); 787 int32 askOnceOnly = kNotConfirmed; 788 for (int32 index = 0; index < numItems; index++) { 789 // we could check for this while iterating through items in each of 790 // the copy loops, except it takes forever to call CalcItemsAndSize 791 BEntry entry((entry_ref*)srcList->ItemAt(index)); 792 if (IsDisksWindowIcon(&entry)) { 793 BString errorStr; 794 if (moveMode == kCreateLink) { 795 errorStr.SetTo( 796 B_TRANSLATE("You cannot create a link to the root " 797 "directory.")); 798 } else { 799 errorStr.SetTo( 800 B_TRANSLATE("You cannot copy or move the root " 801 "directory.")); 802 } 803 804 BAlert* alert = new BAlert("", errorStr.String(), 805 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 806 B_WARNING_ALERT); 807 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 808 alert->Go(); 809 return B_ERROR; 810 } 811 if (moveMode == kMoveSelectionTo 812 && !ConfirmChangeIfWellKnownDirectory(&entry, kMove, 813 false, &askOnceOnly)) { 814 return B_ERROR; 815 } 816 } 817 818 if (preflightNameCheck) { 819 ASSERT(collisionCount); 820 ASSERT(preflightResult); 821 822 *preflightResult = kPrompt; 823 *collisionCount = 0; 824 825 *preflightResult = PreFlightNameCheck(srcList, destDir, 826 collisionCount, moveMode); 827 if (*preflightResult == kCanceled) { 828 // user canceled 829 return B_ERROR; 830 } 831 } 832 833 // set up the status display 834 switch (moveMode) { 835 case kCopySelectionTo: 836 case kDuplicateSelection: 837 case kMoveSelectionTo: 838 { 839 loopControl->Init(moveMode == kMoveSelectionTo ? kMoveState 840 : kCopyState); 841 842 int32 totalItems = 0; 843 off_t totalSize = 0; 844 if (needSizeCalculation) { 845 if (CalcItemsAndSize(loopControl, srcList, 846 dstVol->BlockSize(), &totalItems, &totalSize) 847 != B_OK) { 848 return B_ERROR; 849 } 850 851 // check for free space before starting copy 852 if ((totalSize + (4* kKBSize)) >= dstVol->FreeBytes()) { 853 BAlert* alert = new BAlert("", 854 B_TRANSLATE_NOCOLLECT(kNoFreeSpace), 855 B_TRANSLATE("Cancel"), 856 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 857 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 858 alert->Go(); 859 return B_ERROR; 860 } 861 } 862 863 loopControl->Init(totalItems, totalSize, destRef); 864 break; 865 } 866 867 case kCreateLink: 868 if (numItems > 10) { 869 // this will be fast, only put up status if lots of items 870 // moved, links created 871 loopControl->Init(kCreateLinkState); 872 loopControl->Init(numItems, numItems, destRef); 873 } 874 break; 875 } 876 877 return B_OK; 878 } 879 880 881 // ToDo: 882 // get rid of this cruft 883 bool 884 delete_ref(void* ref) 885 { 886 delete (entry_ref*)ref; 887 return false; 888 } 889 890 891 bool 892 delete_point(void* point) 893 { 894 delete (BPoint*)point; 895 return false; 896 } 897 898 899 static status_t 900 MoveTask(BObjectList<entry_ref>* srcList, BEntry* destEntry, BList* pointList, 901 uint32 moveMode) 902 { 903 ASSERT(!srcList->IsEmpty()); 904 905 // extract information from src, dest models 906 // ## note that we're assuming all items come from the same volume 907 // ## by looking only at FirstItem here which is not a good idea 908 dev_t srcVolumeDevice = srcList->FirstItem()->device; 909 dev_t destVolumeDevice = srcVolumeDevice; 910 911 StatStruct deststat; 912 BVolume volume(srcVolumeDevice); 913 entry_ref destRef; 914 915 bool destIsTrash = false; 916 BDirectory destDir; 917 BDirectory* destDirToCheck = NULL; 918 bool needPreflightNameCheck = false; 919 bool sourceIsReadOnly = volume.IsReadOnly(); 920 volume.Unset(); 921 922 bool fromUndo = FSIsUndoMoveMode(moveMode); 923 moveMode = FSMoveMode(moveMode); 924 925 // if we're not passed a destEntry then we are supposed to move to trash 926 if (destEntry != NULL) { 927 destEntry->GetRef(&destRef); 928 929 destDir.SetTo(destEntry); 930 destDir.GetStat(&deststat); 931 destDirToCheck = &destDir; 932 933 destVolumeDevice = deststat.st_dev; 934 destIsTrash = FSIsTrashDir(destEntry); 935 volume.SetTo(destVolumeDevice); 936 937 needPreflightNameCheck = true; 938 } else if (moveMode == kDuplicateSelection) { 939 BEntry entry; 940 entry.SetTo(srcList->FirstItem()); 941 entry.GetParent(&destDir); 942 volume.SetTo(srcVolumeDevice); 943 } else { 944 // move is to trash 945 destIsTrash = true; 946 947 FSGetTrashDir(&destDir, srcVolumeDevice); 948 volume.SetTo(srcVolumeDevice); 949 950 BEntry entry; 951 destDir.GetEntry(&entry); 952 destDirToCheck = &destDir; 953 954 entry.GetRef(&destRef); 955 } 956 957 // change the move mode if needed 958 if (moveMode == kCopySelectionTo && destIsTrash) { 959 // cannot copy to trash 960 moveMode = kMoveSelectionTo; 961 } 962 963 if (moveMode == kMoveSelectionTo && sourceIsReadOnly) 964 moveMode = kCopySelectionTo; 965 966 bool needSizeCalculation = true; 967 if ((moveMode == kMoveSelectionTo && srcVolumeDevice == destVolumeDevice) 968 || destIsTrash) { 969 needSizeCalculation = false; 970 } 971 972 // we need the undo object later on, so we create it no matter 973 // if we really need it or not (it's very lightweight) 974 MoveCopyUndo undo(srcList, destDir, pointList, moveMode); 975 if (fromUndo) 976 undo.Remove(); 977 978 TrackerCopyLoopControl loopControl; 979 980 ConflictCheckResult conflictCheckResult = kPrompt; 981 int32 collisionCount = 0; 982 // TODO: Status item is created in InitCopy(), but it would be kind of 983 // neat to move all that into TrackerCopyLoopControl 984 status_t result = InitCopy(&loopControl, moveMode, srcList, 985 &volume, destDirToCheck, &destRef, needPreflightNameCheck, 986 needSizeCalculation, &collisionCount, &conflictCheckResult); 987 988 loopControl.SetSourceList(srcList); 989 990 if (result == B_OK) { 991 for (int32 i = 0; i < srcList->CountItems(); i++) { 992 BPoint* loc = (BPoint*)-1; 993 // a loc of -1 forces autoplacement, rather than copying the 994 // position of the original node 995 // TODO: 996 // Clean this mess up! 997 // What could be a cleaner design is to pass along some kind 998 // "filter" object that post-processes poses, i.e. adds the 999 // location or other stuff. It should not be a job of the 1000 // copy-engine. 1001 1002 entry_ref* srcRef = srcList->ItemAt(i); 1003 1004 if (moveMode == kDuplicateSelection) { 1005 BEntry entry(srcRef); 1006 entry.GetParent(&destDir); 1007 destDir.GetStat(&deststat); 1008 volume.SetTo(srcRef->device); 1009 } 1010 1011 // handle case where item is dropped into folder it already lives 1012 // in which could happen if dragging from a query window 1013 if (moveMode != kCreateLink 1014 && moveMode != kCreateRelativeLink 1015 && moveMode != kDuplicateSelection 1016 && !destIsTrash 1017 && (srcRef->device == destRef.device 1018 && srcRef->directory == deststat.st_ino)) { 1019 continue; 1020 } 1021 1022 if (loopControl.CheckUserCanceled()) 1023 break; 1024 1025 BEntry sourceEntry(srcRef); 1026 if (sourceEntry.InitCheck() != B_OK) { 1027 BString error(B_TRANSLATE("Error moving \"%name\".")); 1028 error.ReplaceFirst("%name", srcRef->name); 1029 BAlert* alert = new BAlert("", error.String(), 1030 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 1031 B_WARNING_ALERT); 1032 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1033 alert->Go(); 1034 break; 1035 } 1036 1037 // are we moving item to trash? 1038 if (destIsTrash) { 1039 if (pointList != NULL) 1040 loc = (BPoint*)pointList->ItemAt(i); 1041 1042 result = MoveEntryToTrash(&sourceEntry, loc, undo); 1043 if (result != B_OK) { 1044 BString error(B_TRANSLATE("Error moving \"%name\" to Trash. " 1045 "(%error)")); 1046 error.ReplaceFirst("%name", srcRef->name); 1047 error.ReplaceFirst("%error", strerror(result)); 1048 BAlert* alert = new BAlert("", error.String(), 1049 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 1050 B_WARNING_ALERT); 1051 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1052 alert->Go(); 1053 break; 1054 } 1055 continue; 1056 } 1057 1058 // resolve name collisions and hierarchy problems 1059 if (CheckName(moveMode, &sourceEntry, &destDir, 1060 collisionCount > 1, conflictCheckResult) != B_OK) { 1061 // we will skip the current item, because we got a conflict 1062 // and were asked to or because there was some conflict 1063 1064 // update the status because item got skipped and the status 1065 // will not get updated by the move call 1066 loopControl.UpdateStatus(srcRef->name, *srcRef, 1); 1067 1068 continue; 1069 } 1070 1071 // get location to place this item 1072 if (pointList && moveMode != kCopySelectionTo) { 1073 loc = (BPoint*)pointList->ItemAt(i); 1074 1075 BNode* src_node = GetWritableNode(&sourceEntry); 1076 if (src_node && src_node->InitCheck() == B_OK) { 1077 PoseInfo poseInfo; 1078 poseInfo.fInvisible = false; 1079 poseInfo.fInitedDirectory = deststat.st_ino; 1080 poseInfo.fLocation = *loc; 1081 src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, 1082 &poseInfo, sizeof(poseInfo)); 1083 } 1084 delete src_node; 1085 } 1086 1087 if (pointList) 1088 loc = (BPoint*)pointList->ItemAt(i); 1089 1090 result = MoveItem(&sourceEntry, &destDir, loc, moveMode, NULL, 1091 undo, &loopControl); 1092 if (result != B_OK) 1093 break; 1094 } 1095 } 1096 1097 // duplicates of srcList, destFolder were created - dispose them 1098 delete srcList; 1099 delete destEntry; 1100 1101 // delete file location list and all Points within 1102 if (pointList != NULL) { 1103 pointList->DoForEach(delete_point); 1104 delete pointList; 1105 } 1106 1107 return B_OK; 1108 } 1109 1110 1111 class FailWithAlert { 1112 public: 1113 static void FailOnError(status_t error, const char* string, 1114 const char* name = NULL) 1115 { 1116 if (error != B_OK) 1117 throw FailWithAlert(error, string, name); 1118 } 1119 1120 FailWithAlert(status_t error, const char* string, const char* name) 1121 : 1122 fString(string), 1123 fName(name), 1124 fError(error) 1125 { 1126 } 1127 1128 const char* fString; 1129 const char* fName; 1130 status_t fError; 1131 }; 1132 1133 1134 class MoveError { 1135 public: 1136 static void FailOnError(status_t error) 1137 { 1138 if (error != B_OK) 1139 throw MoveError(error); 1140 } 1141 1142 MoveError(status_t error) 1143 : 1144 fError(error) 1145 { 1146 } 1147 1148 status_t fError; 1149 }; 1150 1151 1152 void 1153 CopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir, 1154 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName, 1155 Undo &undo) 1156 { 1157 if (loopControl->SkipEntry(srcFile, true)) 1158 return; 1159 1160 node_ref node; 1161 destDir->GetNodeRef(&node); 1162 BVolume volume(node.device); 1163 1164 // check for free space first 1165 if ((srcStat->st_size + kKBSize) >= volume.FreeBytes()) { 1166 loopControl->FileError(B_TRANSLATE_NOCOLLECT(kNoFreeSpace), "", 1167 B_DEVICE_FULL, false); 1168 throw (status_t)B_DEVICE_FULL; 1169 } 1170 1171 char destName[B_FILE_NAME_LENGTH]; 1172 srcFile->GetName(destName); 1173 entry_ref ref; 1174 srcFile->GetRef(&ref); 1175 1176 loopControl->UpdateStatus(destName, ref, 1024, true); 1177 1178 if (makeOriginalName) { 1179 BString suffix(" "); 1180 suffix << B_TRANSLATE_COMMENT("copy", "filename copy"), 1181 FSMakeOriginalName(destName, destDir, suffix.String()); 1182 undo.UpdateEntry(srcFile, destName); 1183 } 1184 1185 BEntry conflictingEntry; 1186 if (destDir->FindEntry(destName, &conflictingEntry) == B_OK) { 1187 switch (loopControl->OverwriteOnConflict(srcFile, destName, destDir, 1188 false, false)) { 1189 case TrackerCopyLoopControl::kSkip: 1190 // we are about to ignore this entire directory 1191 return; 1192 1193 case TrackerCopyLoopControl::kReplace: 1194 if (!conflictingEntry.IsDirectory()) { 1195 ThrowOnError(conflictingEntry.Remove()); 1196 break; 1197 } 1198 // fall through if not a directory 1199 case TrackerCopyLoopControl::kMerge: 1200 // This flag implies that the attributes should be kept 1201 // on the file. Just ignore it. 1202 break; 1203 } 1204 } 1205 1206 try { 1207 LowLevelCopy(srcFile, srcStat, destDir, destName, loopControl, loc); 1208 } catch (status_t err) { 1209 if (err == kCopyCanceled) 1210 throw (status_t)err; 1211 1212 if (err != B_OK) { 1213 if (!loopControl->FileError( 1214 B_TRANSLATE_NOCOLLECT(kFileErrorString), destName, err, 1215 true)) { 1216 throw (status_t)err; 1217 } else { 1218 // user selected continue in spite of error, update status bar 1219 loopControl->UpdateStatus(NULL, ref, (int32)srcStat->st_size); 1220 } 1221 } 1222 } 1223 } 1224 1225 1226 #ifdef _SILENTLY_CORRECT_FILE_NAMES 1227 static bool 1228 CreateFileSystemCompatibleName(const BDirectory* destDir, char* destName) 1229 { 1230 // Is it a FAT32 file system? 1231 // (this is the only one we currently know about) 1232 1233 BEntry target; 1234 destDir->GetEntry(&target); 1235 entry_ref targetRef; 1236 fs_info info; 1237 if (target.GetRef(&targetRef) == B_OK 1238 && fs_stat_dev(targetRef.device, &info) == B_OK 1239 && !strcmp(info.fsh_name, "fat")) { 1240 bool wasInvalid = false; 1241 1242 // it's a FAT32 file system, now check the name 1243 1244 int32 length = strlen(destName) - 1; 1245 while (destName[length] == '.') { 1246 // invalid name, just cut off the dot at the end 1247 destName[length--] = '\0'; 1248 wasInvalid = true; 1249 } 1250 1251 char* invalid = destName; 1252 while ((invalid = strpbrk(invalid, "?<>\\:\"|*")) != NULL) { 1253 invalid[0] = '_'; 1254 wasInvalid = true; 1255 } 1256 1257 return wasInvalid; 1258 } 1259 1260 return false; 1261 } 1262 #endif 1263 1264 1265 static void 1266 LowLevelCopy(BEntry* srcEntry, StatStruct* srcStat, BDirectory* destDir, 1267 char* destName, CopyLoopControl* loopControl, BPoint* loc) 1268 { 1269 entry_ref ref; 1270 ThrowOnError(srcEntry->GetRef(&ref)); 1271 1272 if (S_ISLNK(srcStat->st_mode)) { 1273 // handle symbolic links 1274 BSymLink srcLink; 1275 BSymLink newLink; 1276 char linkpath[MAXPATHLEN]; 1277 1278 ThrowOnError(srcLink.SetTo(srcEntry)); 1279 ssize_t size = srcLink.ReadLink(linkpath, MAXPATHLEN - 1); 1280 if (size < 0) 1281 ThrowOnError(size); 1282 ThrowOnError(destDir->CreateSymLink(destName, linkpath, &newLink)); 1283 1284 node_ref destNodeRef; 1285 destDir->GetNodeRef(&destNodeRef); 1286 // copy or write new pose location as a first thing 1287 SetUpPoseLocation(ref.directory, destNodeRef.node, &srcLink, 1288 &newLink, loc); 1289 1290 BNodeInfo nodeInfo(&newLink); 1291 nodeInfo.SetType(B_LINK_MIMETYPE); 1292 1293 newLink.SetPermissions(srcStat->st_mode); 1294 newLink.SetOwner(srcStat->st_uid); 1295 newLink.SetGroup(srcStat->st_gid); 1296 newLink.SetModificationTime(srcStat->st_mtime); 1297 newLink.SetCreationTime(srcStat->st_crtime); 1298 1299 return; 1300 } 1301 1302 BFile srcFile(srcEntry, O_RDONLY); 1303 ThrowOnInitCheckError(&srcFile); 1304 1305 const size_t kMinBufferSize = 1024* 128; 1306 const size_t kMaxBufferSize = 1024* 1024; 1307 1308 size_t bufsize = kMinBufferSize; 1309 if ((off_t)bufsize < srcStat->st_size) { 1310 // File bigger than the buffer size: determine an optimal buffer size 1311 system_info sinfo; 1312 get_system_info(&sinfo); 1313 size_t freesize = static_cast<size_t>( 1314 (sinfo.max_pages - sinfo.used_pages) * B_PAGE_SIZE); 1315 bufsize = freesize / 4; 1316 // take 1/4 of RAM max 1317 bufsize -= bufsize % (16* 1024); 1318 // Round to 16 KB boundaries 1319 if (bufsize < kMinBufferSize) { 1320 // at least kMinBufferSize 1321 bufsize = kMinBufferSize; 1322 } else if (bufsize > kMaxBufferSize) { 1323 // no more than kMaxBufferSize 1324 bufsize = kMaxBufferSize; 1325 } 1326 } 1327 1328 BFile destFile(destDir, destName, O_RDWR | O_CREAT); 1329 #ifdef _SILENTLY_CORRECT_FILE_NAMES 1330 if ((destFile.InitCheck() == B_BAD_VALUE 1331 || destFile.InitCheck() == B_NOT_ALLOWED) 1332 && CreateFileSystemCompatibleName(destDir, destName)) { 1333 destFile.SetTo(destDir, destName, B_CREATE_FILE | B_READ_WRITE); 1334 } 1335 #endif 1336 1337 ThrowOnInitCheckError(&destFile); 1338 1339 node_ref destNodeRef; 1340 destDir->GetNodeRef(&destNodeRef); 1341 // copy or write new pose location as a first thing 1342 SetUpPoseLocation(ref.directory, destNodeRef.node, &srcFile, 1343 &destFile, loc); 1344 1345 char* buffer = new char[bufsize]; 1346 try { 1347 // copy data portion of file 1348 while (true) { 1349 if (loopControl->CheckUserCanceled()) { 1350 // if copy was canceled, remove partial destination file 1351 destFile.Unset(); 1352 1353 BEntry destEntry; 1354 if (destDir->FindEntry(destName, &destEntry) == B_OK) 1355 destEntry.Remove(); 1356 1357 throw (status_t)kCopyCanceled; 1358 } 1359 1360 ASSERT(buffer); 1361 ssize_t bytes = srcFile.Read(buffer, bufsize); 1362 1363 if (bytes > 0) { 1364 ssize_t updateBytes = 0; 1365 if (bytes > 32* 1024) { 1366 // when copying large chunks, update after read and after 1367 // write to get better update granularity 1368 updateBytes = bytes / 2; 1369 loopControl->UpdateStatus(NULL, ref, updateBytes, true); 1370 } 1371 1372 loopControl->ChecksumChunk(buffer, (size_t)bytes); 1373 1374 ssize_t result = destFile.Write(buffer, (size_t)bytes); 1375 if (result != bytes) 1376 throw (status_t)B_ERROR; 1377 1378 loopControl->UpdateStatus(NULL, ref, bytes - updateBytes, 1379 true); 1380 } else if (bytes < 0) { 1381 // read error 1382 throw (status_t)bytes; 1383 } else { 1384 // we are done 1385 break; 1386 } 1387 } 1388 1389 CopyAttributes(loopControl, &srcFile, &destFile, buffer, bufsize); 1390 } catch (...) { 1391 delete[] buffer; 1392 throw; 1393 } 1394 1395 destFile.SetPermissions(srcStat->st_mode); 1396 destFile.SetOwner(srcStat->st_uid); 1397 destFile.SetGroup(srcStat->st_gid); 1398 destFile.SetModificationTime(srcStat->st_mtime); 1399 destFile.SetCreationTime(srcStat->st_crtime); 1400 1401 delete[] buffer; 1402 1403 if (!loopControl->ChecksumFile(&ref)) { 1404 // File no good. Remove and quit. 1405 destFile.Unset(); 1406 1407 BEntry destEntry; 1408 if (destDir->FindEntry(destName, &destEntry) == B_OK) 1409 destEntry.Remove(); 1410 throw (status_t)kUserCanceled; 1411 } 1412 } 1413 1414 1415 void 1416 CopyAttributes(CopyLoopControl* control, BNode* srcNode, BNode* destNode, 1417 void* buffer, size_t bufsize) 1418 { 1419 // ToDo: 1420 // Add error checking 1421 // prior to coyping attributes, make sure indices are installed 1422 1423 // When calling CopyAttributes on files, have to make sure destNode 1424 // is a BFile opened R/W 1425 1426 srcNode->RewindAttrs(); 1427 char name[256]; 1428 while (srcNode->GetNextAttrName(name) == B_OK) { 1429 // Check to see if this attribute should be skipped. 1430 if (control->SkipAttribute(name)) 1431 continue; 1432 1433 attr_info info; 1434 if (srcNode->GetAttrInfo(name, &info) != B_OK) 1435 continue; 1436 1437 // Check to see if this attribute should be overwritten when it 1438 // already exists. 1439 if (control->PreserveAttribute(name)) { 1440 attr_info dest_info; 1441 if (destNode->GetAttrInfo(name, &dest_info) == B_OK) 1442 continue; 1443 } 1444 1445 // Special case for a size 0 attribute. It wouldn't be written at all 1446 // otherwise. 1447 if (info.size == 0) 1448 destNode->WriteAttr(name, info.type, 0, buffer, 0); 1449 1450 ssize_t bytes; 1451 ssize_t numToRead = (ssize_t)info.size; 1452 for (off_t offset = 0; numToRead > 0; offset += bytes) { 1453 size_t chunkSize = (size_t)numToRead; 1454 if (chunkSize > bufsize) 1455 chunkSize = bufsize; 1456 1457 bytes = srcNode->ReadAttr(name, info.type, offset, 1458 buffer, chunkSize); 1459 1460 if (bytes <= 0) 1461 break; 1462 1463 destNode->WriteAttr(name, info.type, offset, buffer, 1464 (size_t)bytes); 1465 1466 numToRead -= bytes; 1467 } 1468 } 1469 } 1470 1471 1472 static void 1473 CopyFolder(BEntry* srcEntry, BDirectory* destDir, 1474 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName, 1475 Undo &undo, bool removeSource = false) 1476 { 1477 BDirectory newDir; 1478 BEntry entry; 1479 status_t err = B_OK; 1480 bool createDirectory = true; 1481 BEntry existingEntry; 1482 1483 if (loopControl->SkipEntry(srcEntry, false)) 1484 return; 1485 1486 entry_ref ref; 1487 srcEntry->GetRef(&ref); 1488 1489 char destName[B_FILE_NAME_LENGTH]; 1490 strlcpy(destName, ref.name, sizeof(destName)); 1491 1492 loopControl->UpdateStatus(ref.name, ref, 1024, true); 1493 1494 if (makeOriginalName) { 1495 BString suffix(" "); 1496 suffix << B_TRANSLATE_COMMENT("copy", "filename copy"), 1497 FSMakeOriginalName(destName, destDir, suffix.String()); 1498 undo.UpdateEntry(srcEntry, destName); 1499 } 1500 1501 if (destDir->FindEntry(destName, &existingEntry) == B_OK) { 1502 // some entry with a conflicting name is already present in destDir 1503 // decide what to do about it 1504 bool isDirectory = existingEntry.IsDirectory(); 1505 1506 switch (loopControl->OverwriteOnConflict(srcEntry, destName, destDir, 1507 true, isDirectory)) { 1508 case TrackerCopyLoopControl::kSkip: 1509 // we are about to ignore this entire directory 1510 return; 1511 1512 1513 case TrackerCopyLoopControl::kReplace: 1514 if (!isDirectory) { 1515 // conflicting with a file or symbolic link, remove entry 1516 ThrowOnError(existingEntry.Remove()); 1517 break; 1518 } 1519 // fall through if directory, do not replace. 1520 case TrackerCopyLoopControl::kMerge: 1521 ASSERT(isDirectory); 1522 // do not create a new directory, use the current one 1523 newDir.SetTo(&existingEntry); 1524 createDirectory = false; 1525 break; 1526 } 1527 } 1528 1529 // loop through everything in src folder and copy it to new folder 1530 BDirectory srcDir(srcEntry); 1531 srcDir.Rewind(); 1532 1533 // create a new folder inside of destination folder 1534 if (createDirectory) { 1535 err = destDir->CreateDirectory(destName, &newDir); 1536 #ifdef _SILENTLY_CORRECT_FILE_NAMES 1537 if (err == B_BAD_VALUE) { 1538 // check if it's an invalid name on a FAT32 file system 1539 if (CreateFileSystemCompatibleName(destDir, destName)) 1540 err = destDir->CreateDirectory(destName, &newDir); 1541 } 1542 #endif 1543 if (err != B_OK) { 1544 if (!loopControl->FileError(B_TRANSLATE_NOCOLLECT( 1545 kFolderErrorString), destName, err, true)) { 1546 throw err; 1547 } 1548 1549 // will allow rest of copy to continue 1550 return; 1551 } 1552 } 1553 1554 char* buffer; 1555 if (createDirectory && err == B_OK 1556 && (buffer = (char*)malloc(32768)) != 0) { 1557 CopyAttributes(loopControl, &srcDir, &newDir, buffer, 32768); 1558 // don't copy original pose location if new location passed 1559 free(buffer); 1560 } 1561 1562 StatStruct statbuf; 1563 srcDir.GetStat(&statbuf); 1564 dev_t sourceDeviceID = statbuf.st_dev; 1565 1566 // copy or write new pose location 1567 node_ref destNodeRef; 1568 destDir->GetNodeRef(&destNodeRef); 1569 SetUpPoseLocation(ref.directory, destNodeRef.node, &srcDir, 1570 &newDir, loc); 1571 1572 while (srcDir.GetNextEntry(&entry) == B_OK) { 1573 1574 if (loopControl->CheckUserCanceled()) 1575 throw (status_t)kUserCanceled; 1576 1577 entry.GetStat(&statbuf); 1578 1579 if (S_ISDIR(statbuf.st_mode)) { 1580 1581 // entry is a mount point, do not copy it 1582 if (statbuf.st_dev != sourceDeviceID) { 1583 PRINT(("Avoiding mount point %" B_PRIdDEV ", %" B_PRIdDEV "\n", 1584 statbuf.st_dev, sourceDeviceID)); 1585 continue; 1586 } 1587 1588 CopyFolder(&entry, &newDir, loopControl, 0, false, undo, 1589 removeSource); 1590 if (removeSource) 1591 FSDeleteFolder(&entry, loopControl, true, true, false); 1592 } else if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) { 1593 CopyFile(&entry, &statbuf, &newDir, loopControl, 0, false, undo); 1594 if (removeSource) 1595 entry.Remove(); 1596 } else { 1597 // Ignore special files 1598 } 1599 } 1600 if (removeSource) 1601 srcEntry->Remove(); 1602 else 1603 srcEntry->Unset(); 1604 } 1605 1606 1607 status_t 1608 RecursiveMove(BEntry* entry, BDirectory* destDir, 1609 CopyLoopControl* loopControl) 1610 { 1611 const char* name = entry->Name(); 1612 1613 if (destDir->Contains(name)) { 1614 BPath path (destDir, name); 1615 BDirectory subDir (path.Path()); 1616 entry_ref ref; 1617 entry->GetRef(&ref); 1618 BDirectory source(&ref); 1619 if (source.InitCheck() == B_OK) { 1620 source.Rewind(); 1621 BEntry current; 1622 while (source.GetNextEntry(¤t) == B_OK) { 1623 if (current.IsDirectory()) { 1624 RecursiveMove(¤t, &subDir, loopControl); 1625 current.Remove(); 1626 } else { 1627 name = current.Name(); 1628 if (loopControl->OverwriteOnConflict(¤t, name, 1629 &subDir, true, false) 1630 != TrackerCopyLoopControl::kSkip) { 1631 MoveError::FailOnError(current.MoveTo(&subDir, 1632 NULL, true)); 1633 } 1634 } 1635 } 1636 } 1637 entry->Remove(); 1638 } else 1639 MoveError::FailOnError(entry->MoveTo(destDir)); 1640 1641 return B_OK; 1642 } 1643 1644 status_t 1645 MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc, uint32 moveMode, 1646 const char* newName, Undo &undo, CopyLoopControl* loopControl) 1647 { 1648 entry_ref ref; 1649 try { 1650 node_ref destNode; 1651 StatStruct statbuf; 1652 MoveError::FailOnError(entry->GetStat(&statbuf)); 1653 MoveError::FailOnError(entry->GetRef(&ref)); 1654 MoveError::FailOnError(destDir->GetNodeRef(&destNode)); 1655 1656 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { 1657 PoseInfo poseInfo; 1658 char name[B_FILE_NAME_LENGTH]; 1659 strlcpy(name, ref.name, sizeof(name)); 1660 1661 BSymLink link; 1662 BString suffix(" "); 1663 suffix << B_TRANSLATE_COMMENT("link", "filename link"), 1664 FSMakeOriginalName(name, destDir, suffix.String()); 1665 undo.UpdateEntry(entry, name); 1666 1667 BPath path; 1668 entry->GetPath(&path); 1669 if (loc && loc != (BPoint*)-1) { 1670 poseInfo.fInvisible = false; 1671 poseInfo.fInitedDirectory = destNode.node; 1672 poseInfo.fLocation = *loc; 1673 } 1674 1675 status_t err = B_ERROR; 1676 1677 if (moveMode == kCreateRelativeLink) { 1678 if (statbuf.st_dev == destNode.device) { 1679 // relative link only works on the same device 1680 char oldwd[B_PATH_NAME_LENGTH]; 1681 getcwd(oldwd, B_PATH_NAME_LENGTH); 1682 1683 BEntry destEntry; 1684 destDir -> GetEntry(&destEntry); 1685 BPath destPath; 1686 destEntry.GetPath(&destPath); 1687 1688 chdir(destPath.Path()); 1689 // change working dir to target dir 1690 1691 BString destString(destPath.Path()); 1692 destString.Append("/"); 1693 1694 BString srcString(path.Path()); 1695 srcString.RemoveLast(path.Leaf()); 1696 1697 // find index while paths are the same 1698 1699 const char* src = srcString.String(); 1700 const char* dest = destString.String(); 1701 const char* lastFolderSrc = src; 1702 const char* lastFolderDest = dest; 1703 1704 while (*src && *dest && *src == *dest) { 1705 ++src; 1706 if (*dest++ == '/') { 1707 lastFolderSrc = src; 1708 lastFolderDest = dest; 1709 } 1710 } 1711 src = lastFolderSrc; 1712 dest = lastFolderDest; 1713 1714 BString source; 1715 if (*dest == '\0' && *src != '\0') { 1716 // source is deeper in the same tree than the target 1717 source.Append(src); 1718 } else if (*dest != '\0') { 1719 // target is deeper in the same tree than the source 1720 while (*dest) { 1721 if (*dest == '/') 1722 source.Prepend("../"); 1723 ++dest; 1724 } 1725 source.Append(src); 1726 } 1727 1728 // else source and target are in the same dir 1729 1730 source.Append(path.Leaf()); 1731 err = destDir->CreateSymLink(name, source.String(), 1732 &link); 1733 1734 chdir(oldwd); 1735 // change working dir back to original 1736 } else 1737 moveMode = kCreateLink; 1738 // fall back to absolute link mode 1739 } 1740 1741 if (moveMode == kCreateLink) 1742 err = destDir->CreateSymLink(name, path.Path(), &link); 1743 1744 if (err == B_UNSUPPORTED) { 1745 throw FailWithAlert(err, 1746 B_TRANSLATE("The target disk does not support " 1747 "creating links."), NULL); 1748 } 1749 1750 FailWithAlert::FailOnError(err, 1751 B_TRANSLATE("Error creating link to \"%name\"."), 1752 ref.name); 1753 1754 if (loc && loc != (BPoint*)-1) { 1755 link.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, 1756 sizeof(PoseInfo)); 1757 } 1758 1759 BNodeInfo nodeInfo(&link); 1760 nodeInfo.SetType(B_LINK_MIMETYPE); 1761 return B_OK; 1762 } 1763 1764 // if move is on same volume don't copy 1765 if (statbuf.st_dev == destNode.device && moveMode != kCopySelectionTo 1766 && moveMode != kDuplicateSelection) { 1767 1768 // for "Move" the size for status is always 1 - since file 1769 // size is irrelevant when simply moving to a new folder 1770 loopControl->UpdateStatus(ref.name, ref, 1); 1771 if (entry->IsDirectory()) 1772 return RecursiveMove(entry, destDir, loopControl); 1773 1774 MoveError::FailOnError(entry->MoveTo(destDir, newName)); 1775 } else { 1776 bool makeOriginalName = (moveMode == kDuplicateSelection); 1777 if (S_ISDIR(statbuf.st_mode)) { 1778 CopyFolder(entry, destDir, loopControl, loc, makeOriginalName, 1779 undo, moveMode == kMoveSelectionTo); 1780 } else { 1781 CopyFile(entry, &statbuf, destDir, loopControl, loc, 1782 makeOriginalName, undo); 1783 if (moveMode == kMoveSelectionTo) 1784 entry->Remove(); 1785 } 1786 } 1787 } catch (status_t error) { 1788 // no alert, was already taken care of before 1789 return error; 1790 } catch (MoveError error) { 1791 BString errorString(B_TRANSLATE("Error moving \"%name\"")); 1792 errorString.ReplaceFirst("%name", ref.name); 1793 BAlert* alert = new BAlert("", errorString.String(), B_TRANSLATE("OK"), 1794 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1795 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1796 alert->Go(); 1797 return error.fError; 1798 } catch (FailWithAlert error) { 1799 BString buffer(error.fString); 1800 if (error.fName != NULL) 1801 buffer.ReplaceFirst("%name", error.fName); 1802 else 1803 buffer << error.fString; 1804 1805 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("OK"), 1806 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1807 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1808 alert->Go(); 1809 1810 return error.fError; 1811 } 1812 1813 return B_OK; 1814 } 1815 1816 1817 void 1818 FSDuplicate(BObjectList<entry_ref>* srcList, BList* pointList) 1819 { 1820 LaunchInNewThread("DupTask", B_NORMAL_PRIORITY, MoveTask, srcList, 1821 (BEntry*)NULL, pointList, kDuplicateSelection); 1822 } 1823 1824 1825 #if 0 1826 status_t 1827 FSCopyFolder(BEntry* srcEntry, BDirectory* destDir, 1828 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName) 1829 { 1830 try 1831 CopyFolder(srcEntry, destDir, loopControl, loc, makeOriginalName); 1832 catch (status_t error) { 1833 return error; 1834 1835 return B_OK; 1836 } 1837 #endif 1838 1839 1840 status_t 1841 FSCopyAttributesAndStats(BNode* srcNode, BNode* destNode, bool copyTimes) 1842 { 1843 char* buffer = new char[1024]; 1844 1845 // copy the attributes 1846 srcNode->RewindAttrs(); 1847 char name[256]; 1848 while (srcNode->GetNextAttrName(name) == B_OK) { 1849 attr_info info; 1850 if (srcNode->GetAttrInfo(name, &info) != B_OK) 1851 continue; 1852 1853 attr_info dest_info; 1854 if (destNode->GetAttrInfo(name, &dest_info) == B_OK) 1855 continue; 1856 1857 ssize_t bytes; 1858 ssize_t numToRead = (ssize_t)info.size; 1859 for (off_t offset = 0; numToRead > 0; offset += bytes) { 1860 size_t chunkSize = (size_t)numToRead; 1861 if (chunkSize > 1024) 1862 chunkSize = 1024; 1863 1864 bytes = srcNode->ReadAttr(name, info.type, offset, buffer, 1865 chunkSize); 1866 1867 if (bytes <= 0) 1868 break; 1869 1870 destNode->WriteAttr(name, info.type, offset, buffer, 1871 (size_t)bytes); 1872 1873 numToRead -= bytes; 1874 } 1875 } 1876 delete[] buffer; 1877 1878 // copy the file stats 1879 struct stat srcStat; 1880 srcNode->GetStat(&srcStat); 1881 destNode->SetPermissions(srcStat.st_mode); 1882 destNode->SetOwner(srcStat.st_uid); 1883 destNode->SetGroup(srcStat.st_gid); 1884 if (copyTimes) { 1885 destNode->SetModificationTime(srcStat.st_mtime); 1886 destNode->SetCreationTime(srcStat.st_crtime); 1887 } 1888 1889 return B_OK; 1890 } 1891 1892 1893 #if 0 1894 status_t 1895 FSCopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir, 1896 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName) 1897 { 1898 try { 1899 CopyFile(srcFile, srcStat, destDir, loopControl, loc, 1900 makeOriginalName); 1901 } catch (status_t error) { 1902 return error; 1903 } 1904 1905 return B_OK; 1906 } 1907 #endif 1908 1909 1910 static status_t 1911 MoveEntryToTrash(BEntry* entry, BPoint* loc, Undo &undo) 1912 { 1913 BDirectory trash_dir; 1914 entry_ref ref; 1915 status_t result = entry->GetRef(&ref); 1916 if (result != B_OK) 1917 return result; 1918 1919 node_ref nodeRef; 1920 result = entry->GetNodeRef(&nodeRef); 1921 if (result != B_OK) 1922 return result; 1923 1924 StatStruct statbuf; 1925 result = entry->GetStat(&statbuf); 1926 if (entry->GetStat(&statbuf) != B_OK) 1927 return result; 1928 1929 // if it's a directory close the window and any child dir windows 1930 if (S_ISDIR(statbuf.st_mode)) { 1931 BDirectory dir(entry); 1932 1933 // if it's a volume, try to unmount 1934 if (dir.IsRootDirectory()) { 1935 BVolume volume(nodeRef.device); 1936 BVolume boot; 1937 1938 BVolumeRoster().GetBootVolume(&boot); 1939 if (volume == boot) { 1940 char name[B_FILE_NAME_LENGTH]; 1941 volume.GetName(name); 1942 BString buffer( 1943 B_TRANSLATE("Cannot unmount the boot volume \"%name\".")); 1944 buffer.ReplaceFirst("%name", name); 1945 BAlert* alert = new BAlert("", buffer.String(), 1946 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 1947 B_WARNING_ALERT); 1948 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1949 alert->Go(); 1950 } else { 1951 BMessage message(kUnmountVolume); 1952 message.AddInt32("device_id", volume.Device()); 1953 be_app->PostMessage(&message); 1954 } 1955 return B_OK; 1956 } 1957 1958 // get trash directory on same volume as item being moved 1959 result = FSGetTrashDir(&trash_dir, nodeRef.device); 1960 if (result != B_OK) 1961 return result; 1962 1963 // check hierarchy before moving 1964 BEntry trashEntry; 1965 trash_dir.GetEntry(&trashEntry); 1966 1967 if (dir == trash_dir || dir.Contains(&trashEntry)) { 1968 BAlert* alert = new BAlert("", 1969 B_TRANSLATE("You cannot put the selected item(s) " 1970 "into the trash."), 1971 B_TRANSLATE("OK"), 1972 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1973 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1974 alert->Go(); 1975 1976 // return no error so we don't get two dialogs 1977 return B_OK; 1978 } 1979 1980 BMessage message(kCloseWindowAndChildren); 1981 1982 node_ref parentNode; 1983 parentNode.device = statbuf.st_dev; 1984 parentNode.node = statbuf.st_ino; 1985 message.AddData("node_ref", B_RAW_TYPE, &parentNode, sizeof(node_ref)); 1986 be_app->PostMessage(&message); 1987 } else { 1988 // get trash directory on same volume as item being moved 1989 result = FSGetTrashDir(&trash_dir, nodeRef.device); 1990 if (result != B_OK) 1991 return result; 1992 } 1993 1994 // make sure name doesn't conflict with anything in trash already 1995 char name[B_FILE_NAME_LENGTH]; 1996 strlcpy(name, ref.name, sizeof(name)); 1997 if (trash_dir.Contains(name)) { 1998 BString suffix(" "); 1999 suffix << B_TRANSLATE_COMMENT("copy", "filename copy"), 2000 FSMakeOriginalName(name, &trash_dir, suffix.String()); 2001 undo.UpdateEntry(entry, name); 2002 } 2003 2004 BNode* src_node = 0; 2005 if (loc && loc != (BPoint*)-1 2006 && (src_node = GetWritableNode(entry, &statbuf)) != 0) { 2007 trash_dir.GetStat(&statbuf); 2008 PoseInfo poseInfo; 2009 poseInfo.fInvisible = false; 2010 poseInfo.fInitedDirectory = statbuf.st_ino; 2011 poseInfo.fLocation = *loc; 2012 src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, 2013 sizeof(poseInfo)); 2014 delete src_node; 2015 } 2016 2017 BNode node(entry); 2018 BPath path; 2019 // Get path of entry before it's moved to the trash 2020 // and write it to the file as an attribute 2021 if (node.InitCheck() == B_OK && entry->GetPath(&path) == B_OK) { 2022 BString originalPath(path.Path()); 2023 node.WriteAttrString(kAttrOriginalPath, &originalPath); 2024 } 2025 2026 TrackerCopyLoopControl loopControl; 2027 MoveItem(entry, &trash_dir, loc, kMoveSelectionTo, name, undo, 2028 &loopControl); 2029 return B_OK; 2030 } 2031 2032 2033 ConflictCheckResult 2034 PreFlightNameCheck(BObjectList<entry_ref>* srcList, const BDirectory* destDir, 2035 int32* collisionCount, uint32 moveMode) 2036 { 2037 // count the number of name collisions in dest folder 2038 *collisionCount = 0; 2039 2040 int32 count = srcList->CountItems(); 2041 for (int32 i = 0; i < count; i++) { 2042 entry_ref* srcRef = srcList->ItemAt(i); 2043 BEntry entry(srcRef); 2044 BDirectory parent; 2045 entry.GetParent(&parent); 2046 2047 if (parent != *destDir && destDir->Contains(srcRef->name)) 2048 (*collisionCount)++; 2049 } 2050 2051 // prompt user only if there is more than one collision, otherwise the 2052 // single collision case will be handled as a "Prompt" case by CheckName 2053 if (*collisionCount > 1) { 2054 const char* verb = (moveMode == kMoveSelectionTo) 2055 ? B_TRANSLATE("moving") : B_TRANSLATE("copying"); 2056 BString replaceMsg(B_TRANSLATE_NOCOLLECT(kReplaceManyStr)); 2057 replaceMsg.ReplaceAll("%verb", verb); 2058 2059 BAlert* alert = new BAlert(); 2060 alert->SetText(replaceMsg.String()); 2061 alert->AddButton(B_TRANSLATE("Cancel")); 2062 alert->AddButton(B_TRANSLATE("Prompt")); 2063 alert->AddButton(B_TRANSLATE("Skip all")); 2064 alert->AddButton(B_TRANSLATE("Replace all")); 2065 alert->SetShortcut(0, B_ESCAPE); 2066 switch (alert->Go()) { 2067 case 0: 2068 return kCanceled; 2069 2070 case 1: 2071 // user selected "Prompt" 2072 return kPrompt; 2073 2074 case 2: 2075 // user selected "Skip all" 2076 return kSkipAll; 2077 2078 case 3: 2079 // user selected "Replace all" 2080 return kReplaceAll; 2081 } 2082 } 2083 2084 return kNoConflicts; 2085 } 2086 2087 2088 void 2089 FileStatToString(StatStruct* stat, char* buffer, int32 length) 2090 { 2091 tm timeData; 2092 localtime_r(&stat->st_mtime, &timeData); 2093 2094 BString size; 2095 static BStringFormat format( 2096 B_TRANSLATE("{0, plural, one{# byte} other{# bytes}}")); 2097 format.Format(size, stat->st_size); 2098 uint32 pos = snprintf(buffer, length, "\n\t(%s ", size.String()); 2099 2100 strftime(buffer + pos, length - pos, "%b %d %Y, %I:%M:%S %p)", &timeData); 2101 } 2102 2103 2104 status_t 2105 CheckName(uint32 moveMode, const BEntry* sourceEntry, 2106 const BDirectory* destDir, bool multipleCollisions, 2107 ConflictCheckResult& conflictResolution) 2108 { 2109 if (moveMode == kDuplicateSelection) { 2110 // when duplicating, we will never have a conflict 2111 return B_OK; 2112 } 2113 2114 // see if item already exists in destination dir 2115 const char* name = sourceEntry->Name(); 2116 bool sourceIsDirectory = sourceEntry->IsDirectory(); 2117 2118 BDirectory srcDirectory; 2119 if (sourceIsDirectory) { 2120 srcDirectory.SetTo(sourceEntry); 2121 BEntry destEntry; 2122 destDir->GetEntry(&destEntry); 2123 2124 if (moveMode != kCreateLink && moveMode != kCreateRelativeLink 2125 && (srcDirectory == *destDir 2126 || srcDirectory.Contains(&destEntry))) { 2127 BAlert* alert = new BAlert("", 2128 B_TRANSLATE("You can't move a folder into itself " 2129 "or any of its own sub-folders."), B_TRANSLATE("OK"), 2130 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2131 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2132 alert->Go(); 2133 return B_ERROR; 2134 } 2135 } 2136 2137 if (FSIsTrashDir(sourceEntry) && moveMode != kCreateLink 2138 && moveMode != kCreateRelativeLink) { 2139 BAlert* alert = new BAlert("", 2140 B_TRANSLATE("You can't move or copy the trash."), 2141 B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, 2142 B_WARNING_ALERT); 2143 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2144 alert->Go(); 2145 return B_ERROR; 2146 } 2147 2148 BEntry entry; 2149 if (destDir->FindEntry(name, &entry) != B_OK) { 2150 // no conflict, return 2151 return B_OK; 2152 } 2153 2154 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { 2155 // if we are creating link in the same directory, the conflict will 2156 // be handled later by giving the link a unique name 2157 sourceEntry->GetParent(&srcDirectory); 2158 2159 if (srcDirectory == *destDir) 2160 return B_OK; 2161 } 2162 2163 bool destIsDir = entry.IsDirectory(); 2164 // be sure not to replace the parent directory of the item being moved 2165 if (destIsDir) { 2166 BDirectory targetDir(&entry); 2167 if (targetDir.Contains(sourceEntry)) { 2168 BAlert* alert = new BAlert("", 2169 B_TRANSLATE("You can't replace a folder " 2170 "with one of its sub-folders."), 2171 B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2172 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2173 alert->Go(); 2174 return B_ERROR; 2175 } 2176 } 2177 2178 // ensure that the user isn't trying to replace a file with folder 2179 // or vice-versa 2180 if (moveMode != kCreateLink 2181 && moveMode != kCreateRelativeLink 2182 && destIsDir != sourceIsDirectory) { 2183 BAlert* alert = new BAlert("", sourceIsDirectory 2184 ? B_TRANSLATE("You cannot replace a file with a folder or a " 2185 "symbolic link.") 2186 : B_TRANSLATE("You cannot replace a folder or a symbolic link " 2187 "with a file."), B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, 2188 B_WARNING_ALERT); 2189 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2190 alert->Go(); 2191 return B_ERROR; 2192 } 2193 2194 if (conflictResolution == kSkipAll) 2195 return B_ERROR; 2196 2197 if (conflictResolution != kReplaceAll) { 2198 // prompt user to determine whether to replace or not 2199 BString replaceMsg; 2200 2201 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { 2202 replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kSymLinkReplaceStr)); 2203 replaceMsg.ReplaceFirst("%name", name); 2204 } else if (sourceEntry->IsDirectory()) { 2205 replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kDirectoryReplaceStr)); 2206 replaceMsg.ReplaceFirst("%name", name); 2207 replaceMsg.ReplaceFirst("%verb", 2208 moveMode == kMoveSelectionTo 2209 ? B_TRANSLATE("moving") 2210 : B_TRANSLATE("copying")); 2211 } else { 2212 char sourceBuffer[96], destBuffer[96]; 2213 StatStruct statBuffer; 2214 2215 if (!sourceEntry->IsDirectory() 2216 && sourceEntry->GetStat(&statBuffer) == B_OK) { 2217 FileStatToString(&statBuffer, sourceBuffer, 96); 2218 } else 2219 sourceBuffer[0] = '\0'; 2220 2221 if (!entry.IsDirectory() && entry.GetStat(&statBuffer) == B_OK) 2222 FileStatToString(&statBuffer, destBuffer, 96); 2223 else 2224 destBuffer[0] = '\0'; 2225 2226 replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kReplaceStr)); 2227 replaceMsg.ReplaceAll("%name", name); 2228 replaceMsg.ReplaceFirst("%dest", destBuffer); 2229 replaceMsg.ReplaceFirst("%src", sourceBuffer); 2230 replaceMsg.ReplaceFirst("%movemode", moveMode == kMoveSelectionTo 2231 ? B_TRANSLATE("moving") : B_TRANSLATE("copying")); 2232 } 2233 2234 // special case single collision (don't need Replace All shortcut) 2235 BAlert* alert; 2236 if (multipleCollisions || sourceIsDirectory) { 2237 alert = new BAlert(); 2238 alert->SetText(replaceMsg.String()); 2239 alert->AddButton(B_TRANSLATE("Skip")); 2240 alert->AddButton(B_TRANSLATE("Skip all")); 2241 alert->AddButton(B_TRANSLATE("Replace")); 2242 alert->AddButton(B_TRANSLATE("Replace all")); 2243 switch (alert->Go()) { 2244 case 0: 2245 conflictResolution = kCanceled; 2246 return B_ERROR; 2247 case 1: 2248 conflictResolution = kSkipAll; 2249 return B_ERROR; 2250 case 2: 2251 conflictResolution = kReplace; 2252 break; 2253 case 3: 2254 conflictResolution = kReplaceAll; 2255 break; 2256 } 2257 } else { 2258 alert = new BAlert("", replaceMsg.String(), 2259 B_TRANSLATE("Cancel"), B_TRANSLATE("Replace")); 2260 alert->SetShortcut(0, B_ESCAPE); 2261 switch (alert->Go()) { 2262 case 0: 2263 conflictResolution = kCanceled; 2264 return B_ERROR; 2265 case 1: 2266 conflictResolution = kReplace; 2267 break; 2268 } 2269 } 2270 } 2271 2272 // delete destination item 2273 if (destIsDir) 2274 return B_OK; 2275 2276 status_t status = entry.Remove(); 2277 if (status != B_OK) { 2278 BString error(B_TRANSLATE("There was a problem trying to replace " 2279 "\"%name\". The item might be open or busy.")); 2280 error.ReplaceFirst("%name", name); 2281 BAlert* alert = new BAlert("", error.String(), 2282 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2283 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2284 alert->Go(); 2285 } 2286 2287 return status; 2288 } 2289 2290 2291 status_t 2292 FSDeleteFolder(BEntry* dirEntry, CopyLoopControl* loopControl, 2293 bool updateStatus, bool deleteTopDir, bool upateFileNameInStatus) 2294 { 2295 BDirectory dir(dirEntry); 2296 2297 // loop through everything in folder and delete it, skipping trouble files 2298 BEntry entry; 2299 while (dir.GetNextEntry(&entry) == B_OK) { 2300 entry_ref ref; 2301 entry.GetRef(&ref); 2302 2303 if (loopControl->CheckUserCanceled()) 2304 return kTrashCanceled; 2305 2306 status_t status; 2307 2308 if (entry.IsDirectory()) 2309 status = FSDeleteFolder(&entry, loopControl, updateStatus, true, 2310 upateFileNameInStatus); 2311 else { 2312 status = entry.Remove(); 2313 if (updateStatus) { 2314 loopControl->UpdateStatus(upateFileNameInStatus ? ref.name 2315 : "", ref, 1, true); 2316 } 2317 } 2318 2319 if (status == kTrashCanceled) 2320 return kTrashCanceled; 2321 2322 if (status != B_OK) { 2323 loopControl->FileError(B_TRANSLATE_NOCOLLECT( 2324 kFileDeleteErrorString), ref.name, status, false); 2325 } 2326 } 2327 2328 if (loopControl->CheckUserCanceled()) 2329 return kTrashCanceled; 2330 2331 entry_ref ref; 2332 dirEntry->GetRef(&ref); 2333 2334 if (updateStatus && deleteTopDir) 2335 loopControl->UpdateStatus(NULL, ref, 1); 2336 2337 if (deleteTopDir) 2338 return dirEntry->Remove(); 2339 2340 return B_OK; 2341 } 2342 2343 2344 void 2345 FSMakeOriginalName(BString &string, const BDirectory* destDir, 2346 const char* suffix) 2347 { 2348 if (!destDir->Contains(string.String())) 2349 return; 2350 2351 FSMakeOriginalName(string.LockBuffer(B_FILE_NAME_LENGTH), 2352 const_cast<BDirectory*>(destDir), suffix ? suffix : " copy"); 2353 string.UnlockBuffer(); 2354 } 2355 2356 2357 void 2358 FSMakeOriginalName(char* name, BDirectory* destDir, const char* suffix) 2359 { 2360 char root[B_FILE_NAME_LENGTH]; 2361 char copybase[B_FILE_NAME_LENGTH]; 2362 char temp_name[B_FILE_NAME_LENGTH + 10]; 2363 int32 fnum; 2364 2365 // is this name already original? 2366 if (!destDir->Contains(name)) 2367 return; 2368 2369 // Determine if we're copying a 'copy'. This algorithm isn't perfect. 2370 // If you're copying a file whose REAL name ends with 'copy' then 2371 // this method will return "<filename> 1", not "<filename> copy" 2372 2373 // However, it will correctly handle file that contain 'copy' 2374 // elsewhere in their name. 2375 2376 bool copycopy = false; // are we copying a copy? 2377 int32 len = (int32)strlen(name); 2378 char* p = name + len - 1; // get pointer to end os name 2379 2380 // eat up optional numbers (if were copying "<filename> copy 34") 2381 while ((p > name) && isdigit(*p)) 2382 p--; 2383 2384 // eat up optional spaces 2385 while ((p > name) && isspace(*p)) 2386 p--; 2387 2388 // now look for the phrase " copy" 2389 if (p > name) { 2390 // p points to the last char of the word. For example, 'y' in 'copy' 2391 2392 if ((p - 4 > name) && (strncmp(p - 4, suffix, 5) == 0)) { 2393 // we found 'copy' in the right place. 2394 // so truncate after 'copy' 2395 *(p + 1) = '\0'; 2396 copycopy = true; 2397 2398 // save the 'root' name of the file, for possible later use. 2399 // that is copy everything but trailing " copy". Need to 2400 // NULL terminate after copy 2401 strncpy(root, name, (uint32)((p - name) - 4)); 2402 root[(p - name) - 4] = '\0'; 2403 } 2404 } 2405 2406 if (!copycopy) { 2407 // The name can't be longer than B_FILE_NAME_LENGTH. 2408 // The algoritm adds " copy XX" to the name. That's 8 characters. 2409 // B_FILE_NAME_LENGTH already accounts for NULL termination so we 2410 // don't need to save an extra char at the end. 2411 if (strlen(name) > B_FILE_NAME_LENGTH - 8) { 2412 // name is too long - truncate it! 2413 name[B_FILE_NAME_LENGTH - 8] = '\0'; 2414 } 2415 2416 strcpy(root, name); // save root name 2417 strcat(name, suffix); 2418 } 2419 2420 strcpy(copybase, name); 2421 2422 // if name already exists then add a number 2423 fnum = 1; 2424 strcpy(temp_name, name); 2425 while (destDir->Contains(temp_name)) { 2426 sprintf(temp_name, "%s %" B_PRId32, copybase, ++fnum); 2427 2428 if (strlen(temp_name) > (B_FILE_NAME_LENGTH - 1)) { 2429 // The name has grown too long. Maybe we just went from 2430 // "<filename> copy 9" to "<filename> copy 10" and that extra 2431 // character was too much. The solution is to further 2432 // truncate the 'root' name and continue. 2433 // ??? should we reset fnum or not ??? 2434 root[strlen(root) - 1] = '\0'; 2435 sprintf(temp_name, "%s%s %" B_PRId32, root, suffix, fnum); 2436 } 2437 } 2438 2439 ASSERT((strlen(temp_name) <= (B_FILE_NAME_LENGTH - 1))); 2440 strcpy(name, temp_name); 2441 } 2442 2443 2444 status_t 2445 FSRecursiveCalcSize(BInfoWindow* window, CopyLoopControl* loopControl, 2446 BDirectory* dir, off_t* _runningSize, int32* _fileCount, int32* _dirCount) 2447 { 2448 dir->Rewind(); 2449 BEntry entry; 2450 while (dir->GetNextEntry(&entry) == B_OK) { 2451 // be sure window hasn't closed 2452 if (window && window->StopCalc()) 2453 return B_OK; 2454 2455 if (loopControl->CheckUserCanceled()) 2456 return kUserCanceled; 2457 2458 StatStruct statbuf; 2459 status_t status = entry.GetStat(&statbuf); 2460 if (status != B_OK) 2461 return status; 2462 2463 (*_runningSize) += statbuf.st_blocks* 512; 2464 2465 if (S_ISDIR(statbuf.st_mode)) { 2466 BDirectory subdir(&entry); 2467 (*_dirCount)++; 2468 status = FSRecursiveCalcSize(window, loopControl, &subdir, 2469 _runningSize, _fileCount, _dirCount); 2470 if (status != B_OK) 2471 return status; 2472 } else 2473 (*_fileCount)++; 2474 } 2475 return B_OK; 2476 } 2477 2478 2479 status_t 2480 CalcItemsAndSize(CopyLoopControl* loopControl, 2481 BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount, 2482 off_t* totalSize) 2483 { 2484 int32 fileCount = 0; 2485 int32 dirCount = 0; 2486 2487 // check block size for sanity 2488 if (blockSize < 0) { 2489 // This would point at an error to retrieve the block size from 2490 // the target volume. The code below cannot be used, it is only 2491 // meant to get the block size when item operations happen on 2492 // the source volume. 2493 blockSize = 2048; 2494 } else if (blockSize < 1024) { 2495 blockSize = 1024; 2496 if (entry_ref* ref = refList->ItemAt(0)) { 2497 // TODO: This assumes all entries in the list share the same 2498 // volume... 2499 BVolume volume(ref->device); 2500 if (volume.InitCheck() == B_OK) 2501 blockSize = volume.BlockSize(); 2502 } 2503 } 2504 // File systems like ReiserFS may advertize a large block size, but 2505 // stuff is still packed into blocks, so clamp maximum block size. 2506 if (blockSize > 8192) 2507 blockSize = 8192; 2508 2509 int32 num_items = refList->CountItems(); 2510 for (int32 i = 0; i < num_items; i++) { 2511 entry_ref* ref = refList->ItemAt(i); 2512 BEntry entry(ref); 2513 StatStruct statbuf; 2514 entry.GetStat(&statbuf); 2515 2516 if (loopControl->CheckUserCanceled()) 2517 return kUserCanceled; 2518 2519 if (S_ISDIR(statbuf.st_mode)) { 2520 BDirectory dir(&entry); 2521 dirCount++; 2522 (*totalSize) += blockSize; 2523 status_t result = FSRecursiveCalcSize(NULL, loopControl, &dir, 2524 totalSize, &fileCount, &dirCount); 2525 if (result != B_OK) 2526 return result; 2527 } else { 2528 fileCount++; 2529 (*totalSize) += statbuf.st_size + blockSize; 2530 } 2531 } 2532 2533 *totalCount += (fileCount + dirCount); 2534 return B_OK; 2535 } 2536 2537 2538 status_t 2539 FSGetTrashDir(BDirectory* trashDir, dev_t dev) 2540 { 2541 if (trashDir == NULL) 2542 return B_BAD_VALUE; 2543 2544 BVolume volume(dev); 2545 status_t result = volume.InitCheck(); 2546 if (result != B_OK) 2547 return result; 2548 2549 BPath path; 2550 result = find_directory(B_TRASH_DIRECTORY, &path, false, &volume); 2551 if (result != B_OK) 2552 return result; 2553 2554 result = trashDir->SetTo(path.Path()); 2555 if (result != B_OK) { 2556 // Trash directory does not exist yet, create it. 2557 result = create_directory(path.Path(), 0755); 2558 if (result != B_OK) 2559 return result; 2560 2561 result = trashDir->SetTo(path.Path()); 2562 if (result != B_OK) 2563 return result; 2564 2565 // make Trash directory invisible 2566 StatStruct sbuf; 2567 trashDir->GetStat(&sbuf); 2568 2569 PoseInfo poseInfo; 2570 poseInfo.fInvisible = true; 2571 poseInfo.fInitedDirectory = sbuf.st_ino; 2572 trashDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, 2573 sizeof(PoseInfo)); 2574 } 2575 2576 // set Trash icons (if they haven't already been set) 2577 attr_info attrInfo; 2578 size_t size; 2579 const void* data; 2580 if (trashDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2581 data = GetTrackerResources()->LoadResource('ICON', R_TrashIcon, &size); 2582 if (data != NULL) 2583 trashDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size); 2584 } 2585 2586 if (trashDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2587 data = GetTrackerResources()->LoadResource('MICN', R_TrashIcon, &size); 2588 if (data != NULL) 2589 trashDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size); 2590 } 2591 2592 if (trashDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2593 data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE, 2594 R_TrashIcon, &size); 2595 if (data != NULL) 2596 trashDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size); 2597 } 2598 2599 return B_OK; 2600 } 2601 2602 2603 status_t 2604 FSGetDeskDir(BDirectory* deskDir) 2605 { 2606 if (deskDir == NULL) 2607 return B_BAD_VALUE; 2608 2609 BPath path; 2610 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true); 2611 if (result != B_OK) 2612 return result; 2613 2614 result = deskDir->SetTo(path.Path()); 2615 if (result != B_OK) 2616 return result; 2617 2618 // set Desktop icons (if they haven't already been set) 2619 attr_info attrInfo; 2620 size_t size; 2621 const void* data; 2622 if (deskDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2623 data = GetTrackerResources()->LoadResource('ICON', R_DeskIcon, &size); 2624 if (data != NULL) 2625 deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size); 2626 } 2627 2628 if (deskDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2629 data = GetTrackerResources()->LoadResource('MICN', R_DeskIcon, &size); 2630 if (data != NULL) 2631 deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size); 2632 } 2633 2634 if (deskDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2635 data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE, 2636 R_DeskIcon, &size); 2637 if (data != NULL) 2638 deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size); 2639 } 2640 2641 return B_OK; 2642 } 2643 2644 2645 status_t 2646 FSGetBootDeskDir(BDirectory* deskDir) 2647 { 2648 BVolume bootVolume; 2649 BVolumeRoster().GetBootVolume(&bootVolume); 2650 BPath path; 2651 2652 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true, 2653 &bootVolume); 2654 if (result != B_OK) 2655 return result; 2656 2657 return deskDir->SetTo(path.Path()); 2658 } 2659 2660 2661 static bool 2662 FSIsDirFlavor(const BEntry* entry, directory_which directoryType) 2663 { 2664 StatStruct dir_stat; 2665 StatStruct entry_stat; 2666 BVolume volume; 2667 BPath path; 2668 2669 if (entry->GetStat(&entry_stat) != B_OK) 2670 return false; 2671 2672 if (volume.SetTo(entry_stat.st_dev) != B_OK) 2673 return false; 2674 2675 if (find_directory(directoryType, &path, false, &volume) != B_OK) 2676 return false; 2677 2678 stat(path.Path(), &dir_stat); 2679 2680 return dir_stat.st_ino == entry_stat.st_ino 2681 && dir_stat.st_dev == entry_stat.st_dev; 2682 } 2683 2684 2685 bool 2686 FSIsPrintersDir(const BEntry* entry) 2687 { 2688 return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY); 2689 } 2690 2691 2692 bool 2693 FSIsTrashDir(const BEntry* entry) 2694 { 2695 return FSIsDirFlavor(entry, B_TRASH_DIRECTORY); 2696 } 2697 2698 2699 bool 2700 FSIsDeskDir(const BEntry* entry) 2701 { 2702 BPath path; 2703 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true); 2704 if (result != B_OK) 2705 return false; 2706 2707 BEntry entryToCompare(path.Path()); 2708 return entryToCompare == *entry; 2709 } 2710 2711 2712 bool 2713 FSIsHomeDir(const BEntry* entry) 2714 { 2715 return FSIsDirFlavor(entry, B_USER_DIRECTORY); 2716 } 2717 2718 2719 bool 2720 FSIsRootDir(const BEntry* entry) 2721 { 2722 BPath path(entry); 2723 return path == "/"; 2724 } 2725 2726 2727 bool 2728 DirectoryMatchesOrContains(const BEntry* entry, directory_which which) 2729 { 2730 BPath path; 2731 if (find_directory(which, &path, false, NULL) != B_OK) 2732 return false; 2733 2734 BEntry dirEntry(path.Path()); 2735 if (dirEntry.InitCheck() != B_OK) 2736 return false; 2737 2738 if (dirEntry == *entry) 2739 // root level match 2740 return true; 2741 2742 BDirectory dir(&dirEntry); 2743 return dir.Contains(entry); 2744 } 2745 2746 2747 bool 2748 DirectoryMatchesOrContains(const BEntry* entry, const char* additionalPath, 2749 directory_which which) 2750 { 2751 BPath path; 2752 if (find_directory(which, &path, false, NULL) != B_OK) 2753 return false; 2754 2755 path.Append(additionalPath); 2756 BEntry dirEntry(path.Path()); 2757 if (dirEntry.InitCheck() != B_OK) 2758 return false; 2759 2760 if (dirEntry == *entry) 2761 // root level match 2762 return true; 2763 2764 BDirectory dir(&dirEntry); 2765 return dir.Contains(entry); 2766 } 2767 2768 2769 bool 2770 DirectoryMatches(const BEntry* entry, directory_which which) 2771 { 2772 BPath path; 2773 if (find_directory(which, &path, false, NULL) != B_OK) 2774 return false; 2775 2776 BEntry dirEntry(path.Path()); 2777 if (dirEntry.InitCheck() != B_OK) 2778 return false; 2779 2780 return dirEntry == *entry; 2781 } 2782 2783 2784 bool 2785 DirectoryMatches(const BEntry* entry, const char* additionalPath, 2786 directory_which which) 2787 { 2788 BPath path; 2789 if (find_directory(which, &path, false, NULL) != B_OK) 2790 return false; 2791 2792 path.Append(additionalPath); 2793 BEntry dirEntry(path.Path()); 2794 if (dirEntry.InitCheck() != B_OK) 2795 return false; 2796 2797 return dirEntry == *entry; 2798 } 2799 2800 2801 extern status_t 2802 FSFindTrackerSettingsDir(BPath* path, bool autoCreate) 2803 { 2804 status_t result = find_directory(B_USER_SETTINGS_DIRECTORY, path, 2805 autoCreate); 2806 if (result != B_OK) 2807 return result; 2808 2809 path->Append("Tracker"); 2810 2811 return mkdir(path->Path(), 0777) ? B_OK : errno; 2812 } 2813 2814 2815 bool 2816 FSInTrashDir(const entry_ref* ref) 2817 { 2818 BEntry entry(ref); 2819 if (entry.InitCheck() != B_OK) 2820 return false; 2821 2822 BDirectory trashDir; 2823 if (FSGetTrashDir(&trashDir, ref->device) != B_OK) 2824 return false; 2825 2826 return trashDir.Contains(&entry); 2827 } 2828 2829 2830 void 2831 FSEmptyTrash() 2832 { 2833 if (find_thread("_tracker_empty_trash_") != B_OK) { 2834 resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_", 2835 B_NORMAL_PRIORITY, NULL)); 2836 } 2837 } 2838 2839 2840 status_t 2841 empty_trash(void*) 2842 { 2843 // empty trash on all mounted volumes 2844 status_t status = B_OK; 2845 2846 TrackerCopyLoopControl loopControl(kTrashState); 2847 2848 // calculate the sum total of all items on all volumes in trash 2849 BObjectList<entry_ref> srcList; 2850 int32 totalCount = 0; 2851 off_t totalSize = 0; 2852 2853 BVolumeRoster volumeRoster; 2854 BVolume volume; 2855 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 2856 if (volume.IsReadOnly() || !volume.IsPersistent()) 2857 continue; 2858 2859 BDirectory trashDirectory; 2860 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK) 2861 continue; 2862 2863 BEntry entry; 2864 trashDirectory.GetEntry(&entry); 2865 2866 entry_ref ref; 2867 entry.GetRef(&ref); 2868 srcList.AddItem(&ref); 2869 status = CalcItemsAndSize(&loopControl, &srcList, volume.BlockSize(), 2870 &totalCount, &totalSize); 2871 if (status != B_OK) 2872 break; 2873 2874 srcList.MakeEmpty(); 2875 2876 // don't count trash directory itself 2877 totalCount--; 2878 } 2879 2880 if (status == B_OK) { 2881 loopControl.Init(totalCount, totalCount); 2882 2883 volumeRoster.Rewind(); 2884 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 2885 if (volume.IsReadOnly() || !volume.IsPersistent()) 2886 continue; 2887 2888 BDirectory trashDirectory; 2889 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK) 2890 continue; 2891 2892 BEntry entry; 2893 trashDirectory.GetEntry(&entry); 2894 status = FSDeleteFolder(&entry, &loopControl, true, false); 2895 } 2896 } 2897 2898 if (status != B_OK && status != kTrashCanceled && status != kUserCanceled) { 2899 BAlert* alert = new BAlert("", B_TRANSLATE("Error emptying Trash"), 2900 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2901 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2902 alert->Go(); 2903 } 2904 2905 return B_OK; 2906 } 2907 2908 2909 status_t 2910 _DeleteTask(BObjectList<entry_ref>* list, bool confirm) 2911 { 2912 if (confirm) { 2913 bool dontMoveToTrash = TrackerSettings().DontMoveFilesToTrash(); 2914 2915 if (!dontMoveToTrash) { 2916 BAlert* alert = new BAlert("", 2917 B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr), 2918 B_TRANSLATE("Cancel"), B_TRANSLATE("Move to Trash"), 2919 B_TRANSLATE("Delete"), B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 2920 B_WARNING_ALERT); 2921 2922 alert->SetShortcut(0, B_ESCAPE); 2923 alert->SetShortcut(1, 'm'); 2924 alert->SetShortcut(2, 'd'); 2925 2926 switch (alert->Go()) { 2927 case 0: 2928 delete list; 2929 return B_OK; 2930 case 1: 2931 FSMoveToTrash(list, NULL, false); 2932 return B_OK; 2933 } 2934 } else { 2935 BAlert* alert = new BAlert("", 2936 B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr), 2937 B_TRANSLATE("Cancel"), B_TRANSLATE("Delete"), NULL, 2938 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 2939 2940 alert->SetShortcut(0, B_ESCAPE); 2941 alert->SetShortcut(1, 'd'); 2942 2943 if (!alert->Go()) { 2944 delete list; 2945 return B_OK; 2946 } 2947 } 2948 } 2949 2950 TrackerCopyLoopControl loopControl(kDeleteState); 2951 2952 // calculate the sum total of all items on all volumes in trash 2953 int32 totalItems = 0; 2954 int64 totalSize = 0; 2955 2956 status_t status = CalcItemsAndSize(&loopControl, list, 0, &totalItems, 2957 &totalSize); 2958 if (status == B_OK) { 2959 loopControl.Init(totalItems, totalItems); 2960 2961 int32 count = list->CountItems(); 2962 for (int32 index = 0; index < count; index++) { 2963 entry_ref ref(*list->ItemAt(index)); 2964 BEntry entry(&ref); 2965 loopControl.UpdateStatus(ref.name, ref, 1, true); 2966 if (entry.IsDirectory()) 2967 status = FSDeleteFolder(&entry, &loopControl, true, true, true); 2968 else 2969 status = entry.Remove(); 2970 } 2971 2972 if (status != kTrashCanceled && status != kUserCanceled 2973 && status != B_OK) { 2974 BAlert* alert = new BAlert("", B_TRANSLATE("Error deleting items"), 2975 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 2976 B_WARNING_ALERT); 2977 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2978 alert->Go(); 2979 } 2980 } 2981 2982 delete list; 2983 2984 return B_OK; 2985 } 2986 2987 status_t 2988 FSRecursiveCreateFolder(BPath path) 2989 { 2990 BEntry entry(path.Path()); 2991 if (entry.InitCheck() != B_OK) { 2992 BPath parentPath; 2993 status_t err = path.GetParent(&parentPath); 2994 if (err != B_OK) 2995 return err; 2996 2997 err = FSRecursiveCreateFolder(parentPath); 2998 if (err != B_OK) 2999 return err; 3000 } 3001 3002 entry.SetTo(path.Path()); 3003 if (entry.Exists()) 3004 return B_FILE_EXISTS; 3005 3006 BDirectory parent; 3007 entry.GetParent(&parent); 3008 parent.CreateDirectory(entry.Name(), NULL); 3009 3010 return B_OK; 3011 } 3012 3013 status_t 3014 _RestoreTask(BObjectList<entry_ref>* list) 3015 { 3016 TrackerCopyLoopControl loopControl(kRestoreFromTrashState); 3017 3018 // calculate the sum total of all items that will be restored 3019 int32 totalItems = 0; 3020 int64 totalSize = 0; 3021 3022 status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems, 3023 &totalSize); 3024 if (err == B_OK) { 3025 loopControl.Init(totalItems, totalItems); 3026 3027 int32 count = list->CountItems(); 3028 for (int32 index = 0; index < count; index++) { 3029 entry_ref ref(*list->ItemAt(index)); 3030 BEntry entry(&ref); 3031 BPath originalPath; 3032 3033 loopControl.UpdateStatus(ref.name, ref, 1, true); 3034 3035 if (FSGetOriginalPath(&entry, &originalPath) != B_OK) 3036 continue; 3037 3038 BEntry originalEntry(originalPath.Path()); 3039 BPath parentPath; 3040 err = originalPath.GetParent(&parentPath); 3041 if (err != B_OK) 3042 continue; 3043 BEntry parentEntry(parentPath.Path()); 3044 3045 if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) { 3046 if (FSRecursiveCreateFolder(parentPath) == B_OK) { 3047 originalEntry.SetTo(originalPath.Path()); 3048 if (entry.InitCheck() != B_OK) 3049 continue; 3050 } 3051 } 3052 3053 if (!originalEntry.Exists()) { 3054 BDirectory dir(parentPath.Path()); 3055 if (dir.InitCheck() == B_OK) { 3056 const char* leafName = originalEntry.Name(); 3057 if (entry.MoveTo(&dir, leafName) == B_OK) { 3058 BNode node(&entry); 3059 if (node.InitCheck() == B_OK) 3060 node.RemoveAttr(kAttrOriginalPath); 3061 } 3062 } 3063 } 3064 3065 err = loopControl.CheckUserCanceled(); 3066 if (err != B_OK) 3067 break; 3068 } 3069 } 3070 3071 delete list; 3072 3073 return err; 3074 } 3075 3076 void 3077 FSCreateTrashDirs() 3078 { 3079 BVolume volume; 3080 BVolumeRoster roster; 3081 3082 roster.Rewind(); 3083 while (roster.GetNextVolume(&volume) == B_OK) { 3084 if (volume.IsReadOnly() || !volume.IsPersistent()) 3085 continue; 3086 3087 BDirectory trashDir; 3088 FSGetTrashDir(&trashDir, volume.Device()); 3089 } 3090 } 3091 3092 3093 status_t 3094 FSCreateNewFolder(const entry_ref* ref) 3095 { 3096 node_ref node; 3097 node.device = ref->device; 3098 node.node = ref->directory; 3099 3100 BDirectory dir(&node); 3101 status_t result = dir.InitCheck(); 3102 if (result != B_OK) 3103 return result; 3104 3105 // ToDo: is that really necessary here? 3106 BString name(ref->name); 3107 FSMakeOriginalName(name, &dir, "-"); 3108 3109 BDirectory newDir; 3110 result = dir.CreateDirectory(name.String(), &newDir); 3111 if (result != B_OK) 3112 return result; 3113 3114 BNodeInfo nodeInfo(&newDir); 3115 nodeInfo.SetType(B_DIR_MIMETYPE); 3116 3117 return result; 3118 } 3119 3120 3121 status_t 3122 FSCreateNewFolderIn(const node_ref* dirNode, entry_ref* newRef, 3123 node_ref* newNode) 3124 { 3125 BDirectory dir(dirNode); 3126 status_t result = dir.InitCheck(); 3127 if (result == B_OK) { 3128 char name[B_FILE_NAME_LENGTH]; 3129 strlcpy(name, B_TRANSLATE("New folder"), sizeof(name)); 3130 3131 int32 fnum = 1; 3132 while (dir.Contains(name)) { 3133 // if base name already exists then add a number 3134 // ToDo: 3135 // move this logic ot FSMakeOriginalName 3136 if (++fnum > 9) { 3137 snprintf(name, sizeof(name), B_TRANSLATE("New folder%ld"), 3138 fnum); 3139 } else { 3140 snprintf(name, sizeof(name), B_TRANSLATE("New folder %ld"), 3141 fnum); 3142 } 3143 } 3144 3145 BDirectory newDir; 3146 result = dir.CreateDirectory(name, &newDir); 3147 if (result == B_OK) { 3148 BEntry entry; 3149 newDir.GetEntry(&entry); 3150 entry.GetRef(newRef); 3151 entry.GetNodeRef(newNode); 3152 3153 BNodeInfo nodeInfo(&newDir); 3154 nodeInfo.SetType(B_DIR_MIMETYPE); 3155 3156 // add undo item 3157 NewFolderUndo undo(*newRef); 3158 return B_OK; 3159 } 3160 } 3161 3162 BAlert* alert = new BAlert("", 3163 B_TRANSLATE("Sorry, could not create a new folder."), 3164 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3165 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3166 alert->Go(); 3167 return result; 3168 } 3169 3170 3171 ReadAttrResult 3172 ReadAttr(const BNode* node, const char* hostAttrName, 3173 const char* foreignAttrName, type_code type, off_t offset, void* buffer, 3174 size_t length, void (*swapFunc)(void*), bool isForeign) 3175 { 3176 if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer, 3177 length) == (ssize_t)length) { 3178 return kReadAttrNativeOK; 3179 } 3180 3181 // PRINT(("trying %s\n", foreignAttrName)); 3182 // try the other endianness 3183 if (node->ReadAttr(foreignAttrName, type, offset, buffer, length) 3184 != (ssize_t)length) { 3185 return kReadAttrFailed; 3186 } 3187 3188 // PRINT(("got %s\n", foreignAttrName)); 3189 if (!swapFunc) 3190 return kReadAttrForeignOK; 3191 3192 (swapFunc)(buffer); 3193 // run the endian swapper 3194 3195 return kReadAttrForeignOK; 3196 } 3197 3198 3199 ReadAttrResult 3200 GetAttrInfo(const BNode* node, const char* hostAttrName, 3201 const char* foreignAttrName, type_code* type, size_t* size) 3202 { 3203 attr_info info; 3204 3205 if (node->GetAttrInfo(hostAttrName, &info) == B_OK) { 3206 if (type) 3207 *type = info.type; 3208 if (size) 3209 *size = (size_t)info.size; 3210 3211 return kReadAttrNativeOK; 3212 } 3213 3214 if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) { 3215 if (type) 3216 *type = info.type; 3217 if (size) 3218 *size = (size_t)info.size; 3219 3220 return kReadAttrForeignOK; 3221 } 3222 return kReadAttrFailed; 3223 } 3224 3225 3226 status_t 3227 FSGetParentVirtualDirectoryAware(const BEntry& entry, entry_ref& _ref) 3228 { 3229 node_ref nodeRef; 3230 if (entry.GetNodeRef(&nodeRef) == B_OK) { 3231 if (VirtualDirectoryManager* manager 3232 = VirtualDirectoryManager::Instance()) { 3233 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 3234 if (manager->GetParentDirectoryDefinitionFile(nodeRef, _ref, 3235 nodeRef)) { 3236 return B_OK; 3237 } 3238 } 3239 } 3240 3241 status_t error; 3242 BDirectory parent; 3243 BEntry parentEntry; 3244 if ((error = entry.GetParent(&parent)) != B_OK 3245 || (error = parent.GetEntry(&parentEntry)) != B_OK 3246 || (error = parentEntry.GetRef(&_ref)) != B_OK) { 3247 return error; 3248 } 3249 3250 return B_OK; 3251 } 3252 3253 3254 status_t 3255 FSGetParentVirtualDirectoryAware(const BEntry& entry, BEntry& _entry) 3256 { 3257 node_ref nodeRef; 3258 if (entry.GetNodeRef(&nodeRef) == B_OK) { 3259 if (VirtualDirectoryManager* manager 3260 = VirtualDirectoryManager::Instance()) { 3261 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 3262 entry_ref parentRef; 3263 if (manager->GetParentDirectoryDefinitionFile(nodeRef, parentRef, 3264 nodeRef)) { 3265 return _entry.SetTo(&parentRef); 3266 } 3267 } 3268 } 3269 3270 return entry.GetParent(&_entry); 3271 } 3272 3273 3274 status_t 3275 FSGetParentVirtualDirectoryAware(const BEntry& entry, BNode& _node) 3276 { 3277 entry_ref ref; 3278 status_t error = FSGetParentVirtualDirectoryAware(entry, ref); 3279 if (error == B_OK) 3280 error = _node.SetTo(&ref); 3281 return error; 3282 } 3283 3284 3285 // launching code 3286 3287 static status_t 3288 TrackerOpenWith(const BMessage* refs) 3289 { 3290 BMessage clone(*refs); 3291 3292 ASSERT(dynamic_cast<TTracker*>(be_app) != NULL); 3293 ASSERT(clone.what != 0); 3294 3295 clone.AddInt32("launchUsingSelector", 0); 3296 // runs the Open With window 3297 be_app->PostMessage(&clone); 3298 3299 return B_OK; 3300 } 3301 3302 3303 static void 3304 AsynchLaunchBinder(void (*func)(const entry_ref*, const BMessage*, bool on), 3305 const entry_ref* appRef, const BMessage* refs, bool openWithOK) 3306 { 3307 BMessage* task = new BMessage; 3308 task->AddPointer("function", (void*)func); 3309 task->AddMessage("refs", refs); 3310 task->AddBool("openWithOK", openWithOK); 3311 if (appRef != NULL) 3312 task->AddRef("appRef", appRef); 3313 3314 extern BLooper* gLaunchLooper; 3315 gLaunchLooper->PostMessage(task); 3316 } 3317 3318 3319 static bool 3320 SniffIfGeneric(const entry_ref* ref) 3321 { 3322 BNode node(ref); 3323 char type[B_MIME_TYPE_LENGTH]; 3324 BNodeInfo info(&node); 3325 if (info.GetType(type) == B_OK 3326 && strcasecmp(type, B_FILE_MIME_TYPE) != 0) { 3327 // already has a type and it's not octet stream 3328 return false; 3329 } 3330 3331 BPath path(ref); 3332 if (path.Path()) { 3333 // force a mimeset 3334 node.RemoveAttr(kAttrMIMEType); 3335 update_mime_info(path.Path(), 0, 1, 1); 3336 } 3337 3338 return true; 3339 } 3340 3341 3342 static void 3343 SniffIfGeneric(const BMessage* refs) 3344 { 3345 entry_ref ref; 3346 for (int32 index = 0; ; index++) { 3347 if (refs->FindRef("refs", index, &ref) != B_OK) 3348 break; 3349 SniffIfGeneric(&ref); 3350 } 3351 } 3352 3353 3354 static void 3355 _TrackerLaunchAppWithDocuments(const entry_ref* appRef, const BMessage* refs, 3356 bool openWithOK) 3357 { 3358 team_id team; 3359 3360 status_t error = B_ERROR; 3361 BString alertString; 3362 3363 for (int32 mimesetIt = 0; ; mimesetIt++) { 3364 error = be_roster->Launch(appRef, refs, &team); 3365 if (error == B_ALREADY_RUNNING) 3366 // app already running, not really an error 3367 error = B_OK; 3368 3369 if (error == B_OK) 3370 break; 3371 3372 if (mimesetIt > 0) 3373 break; 3374 3375 // failed to open, try mimesetting the refs and launching again 3376 SniffIfGeneric(refs); 3377 } 3378 3379 if (error == B_OK) { 3380 // close possible parent window, if specified 3381 const node_ref* nodeToClose = 0; 3382 ssize_t numBytes; 3383 if (refs != NULL && refs->FindData("nodeRefsToClose", B_RAW_TYPE, 3384 (const void**)&nodeToClose, &numBytes) == B_OK 3385 && nodeToClose != NULL) { 3386 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 3387 if (tracker != NULL) 3388 tracker->CloseParent(*nodeToClose); 3389 } 3390 } else { 3391 alertString.SetTo(B_TRANSLATE("Could not open \"%name\" (%error). ")); 3392 alertString.ReplaceFirst("%name", appRef->name); 3393 alertString.ReplaceFirst("%error", strerror(error)); 3394 if (refs != NULL && openWithOK && error != B_SHUTTING_DOWN) { 3395 alertString << B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3396 BAlert* alert = new BAlert("", alertString.String(), 3397 B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0, 3398 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3399 alert->SetShortcut(0, B_ESCAPE); 3400 if (alert->Go() == 1) 3401 error = TrackerOpenWith(refs); 3402 } else { 3403 BAlert* alert = new BAlert("", alertString.String(), 3404 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 3405 B_WARNING_ALERT); 3406 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3407 alert->Go(); 3408 } 3409 } 3410 } 3411 3412 extern "C" char** environ; 3413 3414 3415 static status_t 3416 LoaderErrorDetails(const entry_ref* app, BString &details) 3417 { 3418 BPath path; 3419 BEntry appEntry(app, true); 3420 3421 status_t result = appEntry.GetPath(&path); 3422 if (result != B_OK) 3423 return result; 3424 3425 char* argv[2] = { const_cast<char*>(path.Path()), 0}; 3426 3427 port_id errorPort = create_port(1, "Tracker loader error"); 3428 3429 // count environment variables 3430 int32 envCount = 0; 3431 while (environ[envCount] != NULL) 3432 envCount++; 3433 3434 char** flatArgs = NULL; 3435 size_t flatArgsSize; 3436 result = __flatten_process_args((const char**)argv, 1, 3437 environ, &envCount, argv[0], &flatArgs, &flatArgsSize); 3438 if (result != B_OK) 3439 return result; 3440 3441 result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount, 3442 B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0); 3443 if (result == B_OK) { 3444 // we weren't supposed to be able to start the application... 3445 return B_ERROR; 3446 } 3447 3448 // read error message from port and construct details string 3449 3450 ssize_t bufferSize; 3451 3452 do { 3453 bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0); 3454 } while (bufferSize == B_INTERRUPTED); 3455 3456 if (bufferSize <= B_OK) { 3457 delete_port(errorPort); 3458 return bufferSize; 3459 } 3460 3461 uint8* buffer = (uint8*)malloc(bufferSize); 3462 if (buffer == NULL) { 3463 delete_port(errorPort); 3464 return B_NO_MEMORY; 3465 } 3466 3467 bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize, 3468 B_RELATIVE_TIMEOUT, 0); 3469 delete_port(errorPort); 3470 3471 if (bufferSize < B_OK) { 3472 free(buffer); 3473 return bufferSize; 3474 } 3475 3476 BMessage message; 3477 result = message.Unflatten((const char*)buffer); 3478 free(buffer); 3479 3480 if (result != B_OK) 3481 return result; 3482 3483 int32 errorCode = B_ERROR; 3484 result = message.FindInt32("error", &errorCode); 3485 if (result != B_OK) 3486 return result; 3487 3488 const char* detailName = NULL; 3489 switch (errorCode) { 3490 case B_MISSING_LIBRARY: 3491 detailName = "missing library"; 3492 break; 3493 3494 case B_MISSING_SYMBOL: 3495 detailName = "missing symbol"; 3496 break; 3497 } 3498 3499 if (detailName == NULL) 3500 return B_ERROR; 3501 3502 const char* detail; 3503 for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK; 3504 i++) { 3505 if (i > 0) 3506 details += ", "; 3507 details += detail; 3508 } 3509 3510 return B_OK; 3511 } 3512 3513 3514 static void 3515 _TrackerLaunchDocuments(const entry_ref*, const BMessage* refs, 3516 bool openWithOK) 3517 { 3518 if (refs == NULL) 3519 return; 3520 3521 BMessage copyOfRefs(*refs); 3522 3523 entry_ref documentRef; 3524 if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) { 3525 // nothing to launch, we are done 3526 return; 3527 } 3528 3529 status_t error = B_ERROR; 3530 entry_ref app; 3531 BMessage* refsToPass = NULL; 3532 BString alertString; 3533 const char* alternative = 0; 3534 3535 for (int32 mimesetIt = 0; ; mimesetIt++) { 3536 alertString = ""; 3537 error = be_roster->FindApp(&documentRef, &app); 3538 3539 if (error != B_OK && mimesetIt == 0) { 3540 SniffIfGeneric(©OfRefs); 3541 continue; 3542 } 3543 3544 if (error != B_OK) { 3545 alertString.SetTo(B_TRANSLATE("Could not find an application to " 3546 "open \"%name\" (%error). ")); 3547 alertString.ReplaceFirst("%name", documentRef.name); 3548 alertString.ReplaceFirst("%error", strerror(error)); 3549 if (openWithOK) 3550 alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr); 3551 3552 break; 3553 } else { 3554 BEntry appEntry(&app, true); 3555 for (int32 index = 0;;) { 3556 // remove the app itself from the refs received so we don't 3557 // try to open ourselves 3558 entry_ref ref; 3559 if (copyOfRefs.FindRef("refs", index, &ref) != B_OK) 3560 break; 3561 3562 // deal with symlinks properly 3563 BEntry documentEntry(&ref, true); 3564 if (appEntry == documentEntry) { 3565 PRINT(("stripping %s, app %s \n", ref.name, app.name)); 3566 copyOfRefs.RemoveData("refs", index); 3567 } else { 3568 PRINT(("leaving %s, app %s \n", ref.name, app.name)); 3569 index++; 3570 } 3571 } 3572 3573 refsToPass = CountRefs(©OfRefs) > 0 ? ©OfRefs: 0; 3574 team_id team; 3575 error = be_roster->Launch(&app, refsToPass, &team); 3576 if (error == B_ALREADY_RUNNING) 3577 // app already running, not really an error 3578 error = B_OK; 3579 if (error == B_OK || mimesetIt != 0) 3580 break; 3581 3582 SniffIfGeneric(©OfRefs); 3583 } 3584 } 3585 3586 if (error != B_OK && alertString.Length() == 0) { 3587 BString loaderErrorString; 3588 bool openedDocuments = true; 3589 3590 if (!refsToPass) { 3591 // we just double clicked the app itself, do not offer to 3592 // find a handling app 3593 openWithOK = false; 3594 openedDocuments = false; 3595 } 3596 if (error == B_UNKNOWN_EXECUTABLE && !refsToPass) { 3597 // We know it's an executable, but something unsupported 3598 alertString.SetTo(B_TRANSLATE("\"%name\" is an unsupported " 3599 "executable.")); 3600 alertString.ReplaceFirst("%name", app.name); 3601 } else if (error == B_LEGACY_EXECUTABLE && !refsToPass) { 3602 // For the moment, this marks an old R3 binary, we may want to 3603 // extend it to gcc2 binaries someday post R1 3604 alertString.SetTo(B_TRANSLATE("\"%name\" is a legacy executable. " 3605 "Please obtain an updated version or recompile " 3606 "the application.")); 3607 alertString.ReplaceFirst("%name", app.name); 3608 } else if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) { 3609 alertString.SetTo(B_TRANSLATE("Could not open \"%name\". " 3610 "The file is mistakenly marked as executable. ")); 3611 alertString.ReplaceFirst("%name", app.name); 3612 3613 if (!openWithOK) { 3614 // offer the possibility to change the permissions 3615 3616 alertString << B_TRANSLATE("\nShould this be fixed?"); 3617 BAlert* alert = new BAlert("", alertString.String(), 3618 B_TRANSLATE("Cancel"), B_TRANSLATE("Proceed"), 0, 3619 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3620 alert->SetShortcut(0, B_ESCAPE); 3621 if (alert->Go() == 1) { 3622 BEntry entry(&documentRef); 3623 mode_t permissions; 3624 3625 error = entry.GetPermissions(&permissions); 3626 if (error == B_OK) { 3627 error = entry.SetPermissions(permissions 3628 & ~(S_IXUSR | S_IXGRP | S_IXOTH)); 3629 } 3630 if (error == B_OK) { 3631 // we updated the permissions, so let's try again 3632 _TrackerLaunchDocuments(NULL, refs, false); 3633 return; 3634 } else { 3635 alertString.SetTo(B_TRANSLATE("Could not update " 3636 "permissions of file \"%name\". %error")); 3637 alertString.ReplaceFirst("%name", app.name); 3638 alertString.ReplaceFirst("%error", strerror(error)); 3639 } 3640 } else 3641 return; 3642 } 3643 3644 alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr); 3645 } else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) { 3646 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3647 "because application \"%app\" is in the Trash. ")); 3648 alertString.ReplaceFirst("%document", documentRef.name); 3649 alertString.ReplaceFirst("%app", app.name); 3650 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3651 } else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) { 3652 alertString.SetTo( 3653 B_TRANSLATE("Could not open \"%name\" (%error). ")); 3654 alertString.ReplaceFirst("%name", documentRef.name); 3655 alertString.ReplaceFirst("%error", strerror(error)); 3656 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3657 } else if (error == B_MISSING_SYMBOL 3658 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { 3659 if (openedDocuments) { 3660 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3661 "with application \"%app\" (Missing symbol: %symbol). " 3662 "\n")); 3663 alertString.ReplaceFirst("%document", documentRef.name); 3664 alertString.ReplaceFirst("%app", app.name); 3665 alertString.ReplaceFirst("%symbol", 3666 loaderErrorString.String()); 3667 } else { 3668 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3669 "(Missing symbol: %symbol). \n")); 3670 alertString.ReplaceFirst("%document", documentRef.name); 3671 alertString.ReplaceFirst("%symbol", 3672 loaderErrorString.String()); 3673 } 3674 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3675 } else if (error == B_MISSING_LIBRARY 3676 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { 3677 if (openedDocuments) { 3678 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3679 "with application \"%app\" (Missing libraries: %library). " 3680 "\n")); 3681 alertString.ReplaceFirst("%document", documentRef.name); 3682 alertString.ReplaceFirst("%app", app.name); 3683 alertString.ReplaceFirst("%library", 3684 loaderErrorString.String()); 3685 } else { 3686 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3687 "(Missing libraries: %library). \n")); 3688 alertString.ReplaceFirst("%document", documentRef.name); 3689 alertString.ReplaceFirst("%library", 3690 loaderErrorString.String()); 3691 } 3692 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3693 } else { 3694 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" with " 3695 "application \"%app\" (%error). ")); 3696 alertString.ReplaceFirst("%document", documentRef.name); 3697 alertString.ReplaceFirst("%app", app.name); 3698 alertString.ReplaceFirst("%error", strerror(error)); 3699 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3700 } 3701 } 3702 3703 if (error != B_OK) { 3704 if (openWithOK) { 3705 ASSERT(alternative); 3706 alertString << alternative; 3707 BAlert* alert = new BAlert("", alertString.String(), 3708 B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0, 3709 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3710 alert->SetShortcut(0, B_ESCAPE); 3711 if (alert->Go() == 1) 3712 error = TrackerOpenWith(refs); 3713 } else { 3714 BAlert* alert = new BAlert("", alertString.String(), 3715 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 3716 B_WARNING_ALERT); 3717 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3718 alert->Go(); 3719 } 3720 } 3721 } 3722 3723 // the following three calls don't return any reasonable error codes, 3724 // should fix that, making them void 3725 3726 status_t 3727 TrackerLaunch(const entry_ref* appRef, const BMessage* refs, bool async, 3728 bool openWithOK) 3729 { 3730 if (!async) 3731 _TrackerLaunchAppWithDocuments(appRef, refs, openWithOK); 3732 else { 3733 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs, 3734 openWithOK); 3735 } 3736 3737 return B_OK; 3738 } 3739 3740 status_t 3741 TrackerLaunch(const entry_ref* appRef, bool async) 3742 { 3743 if (!async) 3744 _TrackerLaunchAppWithDocuments(appRef, NULL, false); 3745 else 3746 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false); 3747 3748 return B_OK; 3749 } 3750 3751 status_t 3752 TrackerLaunch(const BMessage* refs, bool async, bool openWithOK) 3753 { 3754 if (!async) 3755 _TrackerLaunchDocuments(NULL, refs, openWithOK); 3756 else 3757 AsynchLaunchBinder(&_TrackerLaunchDocuments, NULL, refs, openWithOK); 3758 3759 return B_OK; 3760 } 3761 3762 status_t 3763 LaunchBrokenLink(const char* signature, const BMessage* refs) 3764 { 3765 // This call is to support a hacky workaround for double-clicking 3766 // broken refs for cifs 3767 be_roster->Launch(signature, const_cast<BMessage*>(refs)); 3768 return B_OK; 3769 } 3770 3771 3772 // external launch calls; need to be robust, work if Tracker is not running 3773 3774 3775 #if !B_BEOS_VERSION_DANO 3776 _IMPEXP_TRACKER 3777 #endif 3778 status_t 3779 FSLaunchItem(const entry_ref* application, const BMessage* refsReceived, 3780 bool async, bool openWithOK) 3781 { 3782 return TrackerLaunch(application, refsReceived, async, openWithOK); 3783 } 3784 3785 3786 #if !B_BEOS_VERSION_DANO 3787 _IMPEXP_TRACKER 3788 #endif 3789 status_t 3790 FSOpenWith(BMessage* listOfRefs) 3791 { 3792 status_t result = B_ERROR; 3793 listOfRefs->what = B_REFS_RECEIVED; 3794 3795 if (dynamic_cast<TTracker*>(be_app) != NULL) 3796 result = TrackerOpenWith(listOfRefs); 3797 else 3798 ASSERT(!"not yet implemented"); 3799 3800 return result; 3801 } 3802 3803 3804 // legacy calls, need for compatibility 3805 3806 3807 void 3808 FSOpenWithDocuments(const entry_ref* executable, BMessage* documents) 3809 { 3810 TrackerLaunch(executable, documents, true); 3811 delete documents; 3812 } 3813 3814 3815 status_t 3816 FSLaunchUsing(const entry_ref* ref, BMessage* listOfRefs) 3817 { 3818 BMessage temp(B_REFS_RECEIVED); 3819 if (listOfRefs == NULL) { 3820 ASSERT(ref != NULL); 3821 temp.AddRef("refs", ref); 3822 listOfRefs = &temp; 3823 } 3824 FSOpenWith(listOfRefs); 3825 3826 return B_OK; 3827 } 3828 3829 3830 status_t 3831 FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32, bool async) 3832 { 3833 if (refs != NULL) 3834 refs->what = B_REFS_RECEIVED; 3835 3836 status_t result = TrackerLaunch(appRef, refs, async, true); 3837 delete refs; 3838 3839 return result; 3840 } 3841 3842 3843 void 3844 FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32 workspace) 3845 { 3846 FSLaunchItem(appRef, refs, workspace, true); 3847 } 3848 3849 3850 // Get the original path of an entry in the trash 3851 status_t 3852 FSGetOriginalPath(BEntry* entry, BPath* result) 3853 { 3854 status_t err; 3855 entry_ref ref; 3856 err = entry->GetRef(&ref); 3857 if (err != B_OK) 3858 return err; 3859 3860 // Only call the routine for entries in the trash 3861 if (!FSInTrashDir(&ref)) 3862 return B_ERROR; 3863 3864 BNode node(entry); 3865 BString originalPath; 3866 if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) { 3867 // We're in luck, the entry has the original path in an attribute 3868 err = result->SetTo(originalPath.String()); 3869 return err; 3870 } 3871 3872 // Iterate the parent directories to find one with 3873 // the original path attribute 3874 BEntry parent(*entry); 3875 err = parent.InitCheck(); 3876 if (err != B_OK) 3877 return err; 3878 3879 // walk up the directory structure until we find a node 3880 // with original path attribute 3881 do { 3882 // move to the parent of this node 3883 err = parent.GetParent(&parent); 3884 if (err != B_OK) 3885 return err; 3886 3887 // return if we are at the root of the trash 3888 if (FSIsTrashDir(&parent)) 3889 return B_ENTRY_NOT_FOUND; 3890 3891 // get the parent as a node 3892 err = node.SetTo(&parent); 3893 if (err != B_OK) 3894 return err; 3895 } while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK); 3896 3897 // Found the attribute, figure out there this file 3898 // used to live, based on the successfully-read attribute 3899 err = result->SetTo(originalPath.String()); 3900 if (err != B_OK) 3901 return err; 3902 3903 BPath path, pathParent; 3904 err = parent.GetPath(&pathParent); 3905 if (err != B_OK) 3906 return err; 3907 3908 err = entry->GetPath(&path); 3909 if (err != B_OK) 3910 return err; 3911 3912 result->Append(path.Path() + strlen(pathParent.Path()) + 1); 3913 // compute the new path by appending the offset of 3914 // the item we are locating, to the original path 3915 // of the parent 3916 3917 return B_OK; 3918 } 3919 3920 3921 directory_which 3922 WellKnowEntryList::Match(const node_ref* node) 3923 { 3924 const WellKnownEntry* result = MatchEntry(node); 3925 if (result != NULL) 3926 return result->which; 3927 3928 return (directory_which)-1; 3929 } 3930 3931 3932 const WellKnowEntryList::WellKnownEntry* 3933 WellKnowEntryList::MatchEntry(const node_ref* node) 3934 { 3935 if (self == NULL) 3936 self = new WellKnowEntryList(); 3937 3938 return self->MatchEntryCommon(node); 3939 } 3940 3941 3942 const WellKnowEntryList::WellKnownEntry* 3943 WellKnowEntryList::MatchEntryCommon(const node_ref* node) 3944 { 3945 uint32 count = entries.size(); 3946 for (uint32 index = 0; index < count; index++) { 3947 if (*node == entries[index].node) 3948 return &entries[index]; 3949 } 3950 3951 return NULL; 3952 } 3953 3954 3955 void 3956 WellKnowEntryList::Quit() 3957 { 3958 delete self; 3959 self = NULL; 3960 } 3961 3962 3963 void 3964 WellKnowEntryList::AddOne(directory_which which, const char* name) 3965 { 3966 BPath path; 3967 if (find_directory(which, &path, true) != B_OK) 3968 return; 3969 3970 BEntry entry(path.Path(), true); 3971 node_ref node; 3972 if (entry.GetNodeRef(&node) != B_OK) 3973 return; 3974 3975 entries.push_back(WellKnownEntry(&node, which, name)); 3976 } 3977 3978 3979 void 3980 WellKnowEntryList::AddOne(directory_which which, directory_which base, 3981 const char* extra, const char* name) 3982 { 3983 BPath path; 3984 if (find_directory(base, &path, true) != B_OK) 3985 return; 3986 3987 path.Append(extra); 3988 BEntry entry(path.Path(), true); 3989 node_ref node; 3990 if (entry.GetNodeRef(&node) != B_OK) 3991 return; 3992 3993 entries.push_back(WellKnownEntry(&node, which, name)); 3994 } 3995 3996 3997 void 3998 WellKnowEntryList::AddOne(directory_which which, const char* path, 3999 const char* name) 4000 { 4001 BEntry entry(path, true); 4002 node_ref node; 4003 if (entry.GetNodeRef(&node) != B_OK) 4004 return; 4005 4006 entries.push_back(WellKnownEntry(&node, which, name)); 4007 } 4008 4009 4010 WellKnowEntryList::WellKnowEntryList() 4011 { 4012 AddOne(B_SYSTEM_DIRECTORY, "system"); 4013 AddOne((directory_which)B_BOOT_DISK, "/boot", "boot"); 4014 AddOne(B_USER_DIRECTORY, "home"); 4015 4016 AddOne(B_BEOS_FONTS_DIRECTORY, "fonts"); 4017 AddOne(B_USER_FONTS_DIRECTORY, "fonts"); 4018 4019 AddOne(B_BEOS_APPS_DIRECTORY, "apps"); 4020 AddOne(B_APPS_DIRECTORY, "apps"); 4021 AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY, 4022 B_USER_DESKBAR_DIRECTORY, "Applications", "apps"); 4023 4024 AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences"); 4025 AddOne(B_PREFERENCES_DIRECTORY, "preferences"); 4026 AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY, 4027 B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences"); 4028 4029 AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail", 4030 "mail"); 4031 4032 AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY, 4033 "queries", "queries"); 4034 4035 AddOne(B_SYSTEM_DEVELOP_DIRECTORY, "develop"); 4036 AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY, 4037 B_USER_DESKBAR_DIRECTORY, "Development", "develop"); 4038 4039 AddOne(B_USER_CONFIG_DIRECTORY, "config"); 4040 4041 AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY, 4042 "people", "people"); 4043 4044 AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY, 4045 "downloads", "downloads"); 4046 } 4047 4048 WellKnowEntryList* WellKnowEntryList::self = NULL; 4049 4050 } // namespace BPrivate 4051