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