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