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