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