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