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