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 BAlert* alert = new BAlert("", 3026 B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr), 3027 B_TRANSLATE("Cancel"), B_TRANSLATE("Move to Trash"), 3028 B_TRANSLATE("Delete"), 3029 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 3030 3031 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3032 alert->SetShortcut(0, B_ESCAPE); 3033 alert->SetShortcut(1, 'm'); 3034 alert->SetShortcut(2, 'd'); 3035 3036 switch (alert->Go()) { 3037 case 0: 3038 delete list; 3039 return B_CANCELED; 3040 3041 case 1: 3042 default: 3043 FSMoveToTrash(list, NULL, false); 3044 return B_OK; 3045 3046 case 2: 3047 break; 3048 } 3049 } 3050 3051 TrackerCopyLoopControl loopControl(kDeleteState); 3052 3053 // calculate the sum total of all items on all volumes in trash 3054 int32 totalItems = 0; 3055 int64 totalSize = 0; 3056 3057 status_t status = CalcItemsAndSize(&loopControl, list, 0, &totalItems, 3058 &totalSize); 3059 if (status == B_OK) { 3060 loopControl.Init(totalItems, totalItems); 3061 3062 int32 count = list->CountItems(); 3063 for (int32 index = 0; index < count; index++) { 3064 entry_ref ref(*list->ItemAt(index)); 3065 BEntry entry(&ref); 3066 loopControl.UpdateStatus(ref.name, ref, 1, true); 3067 if (entry.IsDirectory()) 3068 status = FSDeleteFolder(&entry, &loopControl, true, true, true); 3069 else 3070 status = entry.Remove(); 3071 } 3072 3073 if (status != kTrashCanceled && status != kUserCanceled 3074 && status != B_OK) { 3075 BAlert* alert = new BAlert("", B_TRANSLATE("Error deleting items"), 3076 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 3077 B_WARNING_ALERT); 3078 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3079 alert->Go(); 3080 } 3081 } 3082 3083 delete list; 3084 3085 return B_OK; 3086 } 3087 3088 status_t 3089 FSRecursiveCreateFolder(BPath path) 3090 { 3091 BEntry entry(path.Path()); 3092 if (entry.InitCheck() != B_OK) { 3093 BPath parentPath; 3094 status_t err = path.GetParent(&parentPath); 3095 if (err != B_OK) 3096 return err; 3097 3098 err = FSRecursiveCreateFolder(parentPath); 3099 if (err != B_OK) 3100 return err; 3101 } 3102 3103 entry.SetTo(path.Path()); 3104 if (entry.Exists()) 3105 return B_FILE_EXISTS; 3106 3107 BDirectory parent; 3108 entry.GetParent(&parent); 3109 parent.CreateDirectory(entry.Name(), NULL); 3110 3111 return B_OK; 3112 } 3113 3114 status_t 3115 _RestoreTask(BObjectList<entry_ref>* list) 3116 { 3117 TrackerCopyLoopControl loopControl(kRestoreFromTrashState); 3118 3119 // calculate the sum total of all items that will be restored 3120 int32 totalItems = 0; 3121 int64 totalSize = 0; 3122 3123 status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems, 3124 &totalSize); 3125 if (err == B_OK) { 3126 loopControl.Init(totalItems, totalItems); 3127 3128 int32 count = list->CountItems(); 3129 for (int32 index = 0; index < count; index++) { 3130 entry_ref ref(*list->ItemAt(index)); 3131 BEntry entry(&ref); 3132 BPath originalPath; 3133 3134 loopControl.UpdateStatus(ref.name, ref, 1, true); 3135 3136 if (FSGetOriginalPath(&entry, &originalPath) != B_OK) 3137 continue; 3138 3139 BEntry originalEntry(originalPath.Path()); 3140 BPath parentPath; 3141 err = originalPath.GetParent(&parentPath); 3142 if (err != B_OK) 3143 continue; 3144 BEntry parentEntry(parentPath.Path()); 3145 3146 if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) { 3147 if (FSRecursiveCreateFolder(parentPath) == B_OK) { 3148 originalEntry.SetTo(originalPath.Path()); 3149 if (entry.InitCheck() != B_OK) 3150 continue; 3151 } 3152 } 3153 3154 if (!originalEntry.Exists()) { 3155 BDirectory dir(parentPath.Path()); 3156 if (dir.InitCheck() == B_OK) { 3157 const char* leafName = originalEntry.Name(); 3158 if (entry.MoveTo(&dir, leafName) == B_OK) { 3159 BNode node(&entry); 3160 if (node.InitCheck() == B_OK) 3161 node.RemoveAttr(kAttrOriginalPath); 3162 } 3163 } 3164 } 3165 3166 err = loopControl.CheckUserCanceled(); 3167 if (err != B_OK) 3168 break; 3169 } 3170 } 3171 3172 delete list; 3173 3174 return err; 3175 } 3176 3177 void 3178 FSCreateTrashDirs() 3179 { 3180 BVolume volume; 3181 BVolumeRoster roster; 3182 3183 roster.Rewind(); 3184 while (roster.GetNextVolume(&volume) == B_OK) { 3185 if (volume.IsReadOnly() || !volume.IsPersistent()) 3186 continue; 3187 3188 BDirectory trashDir; 3189 FSGetTrashDir(&trashDir, volume.Device()); 3190 } 3191 } 3192 3193 3194 status_t 3195 FSCreateNewFolder(const entry_ref* ref) 3196 { 3197 node_ref node; 3198 node.device = ref->device; 3199 node.node = ref->directory; 3200 3201 BDirectory dir(&node); 3202 status_t result = dir.InitCheck(); 3203 if (result != B_OK) 3204 return result; 3205 3206 // ToDo: is that really necessary here? 3207 BString name(ref->name); 3208 FSMakeOriginalName(name, &dir, "-"); 3209 3210 BDirectory newDir; 3211 result = dir.CreateDirectory(name.String(), &newDir); 3212 if (result != B_OK) 3213 return result; 3214 3215 BNodeInfo nodeInfo(&newDir); 3216 nodeInfo.SetType(B_DIR_MIMETYPE); 3217 3218 return result; 3219 } 3220 3221 3222 status_t 3223 FSCreateNewFolderIn(const node_ref* dirNode, entry_ref* newRef, 3224 node_ref* newNode) 3225 { 3226 BDirectory dir(dirNode); 3227 status_t result = dir.InitCheck(); 3228 if (result == B_OK) { 3229 char name[B_FILE_NAME_LENGTH]; 3230 strlcpy(name, B_TRANSLATE("New folder"), sizeof(name)); 3231 3232 int32 fnum = 1; 3233 while (dir.Contains(name)) { 3234 // if base name already exists then add a number 3235 // TODO: move this logic to FSMakeOriginalName 3236 if (++fnum > 9) { 3237 snprintf(name, sizeof(name), B_TRANSLATE("New folder%ld"), 3238 fnum); 3239 } else { 3240 snprintf(name, sizeof(name), B_TRANSLATE("New folder %ld"), 3241 fnum); 3242 } 3243 } 3244 3245 BDirectory newDir; 3246 result = dir.CreateDirectory(name, &newDir); 3247 if (result == B_OK) { 3248 BEntry entry; 3249 newDir.GetEntry(&entry); 3250 entry.GetRef(newRef); 3251 entry.GetNodeRef(newNode); 3252 3253 BNodeInfo nodeInfo(&newDir); 3254 nodeInfo.SetType(B_DIR_MIMETYPE); 3255 3256 // add undo item 3257 NewFolderUndo undo(*newRef); 3258 return B_OK; 3259 } 3260 } 3261 3262 BAlert* alert = new BAlert("", 3263 B_TRANSLATE("Sorry, could not create a new folder."), 3264 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3265 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3266 alert->Go(); 3267 3268 return result; 3269 } 3270 3271 3272 ReadAttrResult 3273 ReadAttr(const BNode* node, const char* hostAttrName, 3274 const char* foreignAttrName, type_code type, off_t offset, void* buffer, 3275 size_t length, void (*swapFunc)(void*), bool isForeign) 3276 { 3277 if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer, 3278 length) == (ssize_t)length) { 3279 return kReadAttrNativeOK; 3280 } 3281 3282 // PRINT(("trying %s\n", foreignAttrName)); 3283 // try the other endianness 3284 if (node->ReadAttr(foreignAttrName, type, offset, buffer, length) 3285 != (ssize_t)length) { 3286 return kReadAttrFailed; 3287 } 3288 3289 // PRINT(("got %s\n", foreignAttrName)); 3290 if (!swapFunc) 3291 return kReadAttrForeignOK; 3292 3293 (swapFunc)(buffer); 3294 // run the endian swapper 3295 3296 return kReadAttrForeignOK; 3297 } 3298 3299 3300 ReadAttrResult 3301 GetAttrInfo(const BNode* node, const char* hostAttrName, 3302 const char* foreignAttrName, type_code* type, size_t* size) 3303 { 3304 attr_info info; 3305 3306 if (node->GetAttrInfo(hostAttrName, &info) == B_OK) { 3307 if (type) 3308 *type = info.type; 3309 if (size) 3310 *size = (size_t)info.size; 3311 3312 return kReadAttrNativeOK; 3313 } 3314 3315 if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) { 3316 if (type) 3317 *type = info.type; 3318 if (size) 3319 *size = (size_t)info.size; 3320 3321 return kReadAttrForeignOK; 3322 } 3323 return kReadAttrFailed; 3324 } 3325 3326 3327 status_t 3328 FSGetParentVirtualDirectoryAware(const BEntry& entry, entry_ref& _ref) 3329 { 3330 node_ref nodeRef; 3331 if (entry.GetNodeRef(&nodeRef) == B_OK) { 3332 if (VirtualDirectoryManager* manager 3333 = VirtualDirectoryManager::Instance()) { 3334 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 3335 if (manager->GetParentDirectoryDefinitionFile(nodeRef, _ref, 3336 nodeRef)) { 3337 return B_OK; 3338 } 3339 } 3340 } 3341 3342 status_t error; 3343 BDirectory parent; 3344 BEntry parentEntry; 3345 if ((error = entry.GetParent(&parent)) != B_OK 3346 || (error = parent.GetEntry(&parentEntry)) != B_OK 3347 || (error = parentEntry.GetRef(&_ref)) != B_OK) { 3348 return error; 3349 } 3350 3351 return B_OK; 3352 } 3353 3354 3355 status_t 3356 FSGetParentVirtualDirectoryAware(const BEntry& entry, BEntry& _entry) 3357 { 3358 node_ref nodeRef; 3359 if (entry.GetNodeRef(&nodeRef) == B_OK) { 3360 if (VirtualDirectoryManager* manager 3361 = VirtualDirectoryManager::Instance()) { 3362 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 3363 entry_ref parentRef; 3364 if (manager->GetParentDirectoryDefinitionFile(nodeRef, parentRef, 3365 nodeRef)) { 3366 return _entry.SetTo(&parentRef); 3367 } 3368 } 3369 } 3370 3371 return entry.GetParent(&_entry); 3372 } 3373 3374 3375 status_t 3376 FSGetParentVirtualDirectoryAware(const BEntry& entry, BNode& _node) 3377 { 3378 entry_ref ref; 3379 status_t error = FSGetParentVirtualDirectoryAware(entry, ref); 3380 if (error == B_OK) 3381 error = _node.SetTo(&ref); 3382 3383 return error; 3384 } 3385 3386 3387 // launching code 3388 3389 static status_t 3390 TrackerOpenWith(const BMessage* refs) 3391 { 3392 BMessage clone(*refs); 3393 3394 ASSERT(dynamic_cast<TTracker*>(be_app) != NULL); 3395 ASSERT(clone.what != 0); 3396 3397 clone.AddInt32("launchUsingSelector", 0); 3398 // runs the Open With window 3399 be_app->PostMessage(&clone); 3400 3401 return B_OK; 3402 } 3403 3404 3405 static void 3406 AsynchLaunchBinder(void (*func)(const entry_ref*, const BMessage*, bool on), 3407 const entry_ref* appRef, const BMessage* refs, bool openWithOK) 3408 { 3409 BMessage* task = new BMessage; 3410 task->AddPointer("function", (void*)func); 3411 task->AddMessage("refs", refs); 3412 task->AddBool("openWithOK", openWithOK); 3413 if (appRef != NULL) 3414 task->AddRef("appRef", appRef); 3415 3416 extern BLooper* gLaunchLooper; 3417 gLaunchLooper->PostMessage(task); 3418 } 3419 3420 3421 static bool 3422 SniffIfGeneric(const entry_ref* ref) 3423 { 3424 BNode node(ref); 3425 char type[B_MIME_TYPE_LENGTH]; 3426 BNodeInfo info(&node); 3427 if (info.GetType(type) == B_OK 3428 && strcasecmp(type, B_FILE_MIME_TYPE) != 0) { 3429 // already has a type and it's not octet stream 3430 return false; 3431 } 3432 3433 BPath path(ref); 3434 if (path.Path()) { 3435 // force a mimeset 3436 node.RemoveAttr(kAttrMIMEType); 3437 update_mime_info(path.Path(), 0, 1, 1); 3438 } 3439 3440 return true; 3441 } 3442 3443 3444 static void 3445 SniffIfGeneric(const BMessage* refs) 3446 { 3447 entry_ref ref; 3448 for (int32 index = 0; ; index++) { 3449 if (refs->FindRef("refs", index, &ref) != B_OK) 3450 break; 3451 SniffIfGeneric(&ref); 3452 } 3453 } 3454 3455 3456 static void 3457 _TrackerLaunchAppWithDocuments(const entry_ref* appRef, const BMessage* refs, 3458 bool openWithOK) 3459 { 3460 team_id team; 3461 3462 status_t error = B_ERROR; 3463 BString alertString; 3464 3465 for (int32 mimesetIt = 0; ; mimesetIt++) { 3466 error = be_roster->Launch(appRef, refs, &team); 3467 if (error == B_ALREADY_RUNNING) 3468 // app already running, not really an error 3469 error = B_OK; 3470 3471 if (error == B_OK) 3472 break; 3473 3474 if (mimesetIt > 0) 3475 break; 3476 3477 // failed to open, try mimesetting the refs and launching again 3478 SniffIfGeneric(refs); 3479 } 3480 3481 if (error == B_OK) { 3482 // close possible parent window, if specified 3483 const node_ref* nodeToClose = 0; 3484 ssize_t numBytes; 3485 if (refs != NULL && refs->FindData("nodeRefsToClose", B_RAW_TYPE, 3486 (const void**)&nodeToClose, &numBytes) == B_OK 3487 && nodeToClose != NULL) { 3488 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 3489 if (tracker != NULL) 3490 tracker->CloseParent(*nodeToClose); 3491 } 3492 } else { 3493 alertString.SetTo(B_TRANSLATE("Could not open \"%name\" (%error). ")); 3494 alertString.ReplaceFirst("%name", appRef->name); 3495 alertString.ReplaceFirst("%error", strerror(error)); 3496 if (refs != NULL && openWithOK && error != B_SHUTTING_DOWN) { 3497 alertString << B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3498 BAlert* alert = new BAlert("", alertString.String(), 3499 B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0, 3500 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3501 alert->SetShortcut(0, B_ESCAPE); 3502 if (alert->Go() == 1) 3503 error = TrackerOpenWith(refs); 3504 } else { 3505 BAlert* alert = new BAlert("", alertString.String(), 3506 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 3507 B_WARNING_ALERT); 3508 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3509 alert->Go(); 3510 } 3511 } 3512 } 3513 3514 3515 extern "C" char** environ; 3516 3517 3518 static status_t 3519 LoaderErrorDetails(const entry_ref* app, BString &details) 3520 { 3521 BPath path; 3522 BEntry appEntry(app, true); 3523 3524 status_t result = appEntry.GetPath(&path); 3525 if (result != B_OK) 3526 return result; 3527 3528 char* argv[2] = { const_cast<char*>(path.Path()), 0}; 3529 3530 port_id errorPort = create_port(1, "Tracker loader error"); 3531 3532 // count environment variables 3533 int32 envCount = 0; 3534 while (environ[envCount] != NULL) 3535 envCount++; 3536 3537 char** flatArgs = NULL; 3538 size_t flatArgsSize; 3539 result = __flatten_process_args((const char**)argv, 1, 3540 environ, &envCount, argv[0], &flatArgs, &flatArgsSize); 3541 if (result != B_OK) 3542 return result; 3543 3544 result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount, 3545 B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0); 3546 if (result == B_OK) { 3547 // we weren't supposed to be able to start the application... 3548 return B_ERROR; 3549 } 3550 3551 // read error message from port and construct details string 3552 3553 ssize_t bufferSize; 3554 3555 do { 3556 bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0); 3557 } while (bufferSize == B_INTERRUPTED); 3558 3559 if (bufferSize <= B_OK) { 3560 delete_port(errorPort); 3561 return bufferSize; 3562 } 3563 3564 uint8* buffer = (uint8*)malloc(bufferSize); 3565 if (buffer == NULL) { 3566 delete_port(errorPort); 3567 return B_NO_MEMORY; 3568 } 3569 3570 bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize, 3571 B_RELATIVE_TIMEOUT, 0); 3572 delete_port(errorPort); 3573 3574 if (bufferSize < B_OK) { 3575 free(buffer); 3576 return bufferSize; 3577 } 3578 3579 BMessage message; 3580 result = message.Unflatten((const char*)buffer); 3581 free(buffer); 3582 3583 if (result != B_OK) 3584 return result; 3585 3586 int32 errorCode = B_ERROR; 3587 result = message.FindInt32("error", &errorCode); 3588 if (result != B_OK) 3589 return result; 3590 3591 const char* detailName = NULL; 3592 switch (errorCode) { 3593 case B_MISSING_LIBRARY: 3594 detailName = "missing library"; 3595 break; 3596 3597 case B_MISSING_SYMBOL: 3598 detailName = "missing symbol"; 3599 break; 3600 } 3601 3602 if (detailName == NULL) 3603 return B_ERROR; 3604 3605 const char* detail; 3606 for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK; 3607 i++) { 3608 if (i > 0) 3609 details += ", "; 3610 details += detail; 3611 } 3612 3613 return B_OK; 3614 } 3615 3616 3617 static void 3618 _TrackerLaunchDocuments(const entry_ref*, const BMessage* refs, 3619 bool openWithOK) 3620 { 3621 if (refs == NULL) 3622 return; 3623 3624 BMessage copyOfRefs(*refs); 3625 3626 entry_ref documentRef; 3627 if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) { 3628 // nothing to launch, we are done 3629 return; 3630 } 3631 3632 status_t error = B_ERROR; 3633 entry_ref app; 3634 BMessage* refsToPass = NULL; 3635 BString alertString; 3636 const char* alternative = 0; 3637 3638 for (int32 mimesetIt = 0; ; mimesetIt++) { 3639 alertString = ""; 3640 error = be_roster->FindApp(&documentRef, &app); 3641 3642 if (error != B_OK && mimesetIt == 0) { 3643 SniffIfGeneric(©OfRefs); 3644 continue; 3645 } 3646 3647 if (error != B_OK) { 3648 alertString.SetTo(B_TRANSLATE("Could not find an application to " 3649 "open \"%name\" (%error). ")); 3650 alertString.ReplaceFirst("%name", documentRef.name); 3651 alertString.ReplaceFirst("%error", strerror(error)); 3652 if (openWithOK) 3653 alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr); 3654 3655 break; 3656 } else { 3657 BEntry appEntry(&app, true); 3658 for (int32 index = 0;;) { 3659 // remove the app itself from the refs received so we don't 3660 // try to open ourselves 3661 entry_ref ref; 3662 if (copyOfRefs.FindRef("refs", index, &ref) != B_OK) 3663 break; 3664 3665 // deal with symlinks properly 3666 BEntry documentEntry(&ref, true); 3667 if (appEntry == documentEntry) { 3668 PRINT(("stripping %s, app %s \n", ref.name, app.name)); 3669 copyOfRefs.RemoveData("refs", index); 3670 } else { 3671 PRINT(("leaving %s, app %s \n", ref.name, app.name)); 3672 index++; 3673 } 3674 } 3675 3676 refsToPass = CountRefs(©OfRefs) > 0 ? ©OfRefs: 0; 3677 team_id team; 3678 error = be_roster->Launch(&app, refsToPass, &team); 3679 if (error == B_ALREADY_RUNNING) 3680 // app already running, not really an error 3681 error = B_OK; 3682 if (error == B_OK || mimesetIt != 0) 3683 break; 3684 if (error == B_LAUNCH_FAILED_EXECUTABLE) { 3685 BVolume volume(documentRef.device); 3686 if (volume.IsReadOnly()) { 3687 BMimeType type; 3688 error = BMimeType::GuessMimeType(&documentRef, &type); 3689 if (error != B_OK) 3690 break; 3691 error = be_roster->FindApp(type.Type(), &app); 3692 if (error != B_OK) 3693 break; 3694 error = be_roster->Launch(&app, refs, &team); 3695 if (error == B_ALREADY_RUNNING) 3696 // app already running, not really an error 3697 error = B_OK; 3698 if (error == B_OK || mimesetIt != 0) 3699 break; 3700 } 3701 } 3702 3703 SniffIfGeneric(©OfRefs); 3704 } 3705 } 3706 3707 if (error != B_OK && alertString.Length() == 0) { 3708 BString loaderErrorString; 3709 bool openedDocuments = true; 3710 3711 if (!refsToPass) { 3712 // we just double clicked the app itself, do not offer to 3713 // find a handling app 3714 openWithOK = false; 3715 openedDocuments = false; 3716 } 3717 if (error == B_UNKNOWN_EXECUTABLE && !refsToPass) { 3718 // We know it's an executable, but something unsupported 3719 alertString.SetTo(B_TRANSLATE("\"%name\" is an unsupported " 3720 "executable.")); 3721 alertString.ReplaceFirst("%name", app.name); 3722 } else if (error == B_LEGACY_EXECUTABLE && !refsToPass) { 3723 // For the moment, this marks an old R3 binary, we may want to 3724 // extend it to gcc2 binaries someday post R1 3725 alertString.SetTo(B_TRANSLATE("\"%name\" is a legacy executable. " 3726 "Please obtain an updated version or recompile " 3727 "the application.")); 3728 alertString.ReplaceFirst("%name", app.name); 3729 } else if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) { 3730 alertString.SetTo(B_TRANSLATE("Could not open \"%name\". " 3731 "The file is mistakenly marked as executable. ")); 3732 alertString.ReplaceFirst("%name", app.name); 3733 3734 if (!openWithOK) { 3735 // offer the possibility to change the permissions 3736 3737 alertString << B_TRANSLATE("\nShould this be fixed?"); 3738 BAlert* alert = new BAlert("", alertString.String(), 3739 B_TRANSLATE("Cancel"), B_TRANSLATE("Proceed"), 0, 3740 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3741 alert->SetShortcut(0, B_ESCAPE); 3742 if (alert->Go() == 1) { 3743 BEntry entry(&documentRef); 3744 mode_t permissions; 3745 3746 error = entry.GetPermissions(&permissions); 3747 if (error == B_OK) { 3748 error = entry.SetPermissions(permissions 3749 & ~(S_IXUSR | S_IXGRP | S_IXOTH)); 3750 } 3751 if (error == B_OK) { 3752 // we updated the permissions, so let's try again 3753 _TrackerLaunchDocuments(NULL, refs, false); 3754 return; 3755 } else { 3756 alertString.SetTo(B_TRANSLATE("Could not update " 3757 "permissions of file \"%name\". %error")); 3758 alertString.ReplaceFirst("%name", app.name); 3759 alertString.ReplaceFirst("%error", strerror(error)); 3760 } 3761 } else 3762 return; 3763 } 3764 3765 alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr); 3766 } else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) { 3767 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3768 "because application \"%app\" is in the Trash. ")); 3769 alertString.ReplaceFirst("%document", documentRef.name); 3770 alertString.ReplaceFirst("%app", app.name); 3771 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3772 } else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) { 3773 alertString.SetTo( 3774 B_TRANSLATE("Could not open \"%name\" (%error). ")); 3775 alertString.ReplaceFirst("%name", documentRef.name); 3776 alertString.ReplaceFirst("%error", strerror(error)); 3777 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3778 } else if (error == B_MISSING_SYMBOL 3779 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { 3780 if (openedDocuments) { 3781 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3782 "with application \"%app\" (Missing symbol: %symbol). " 3783 "\n")); 3784 alertString.ReplaceFirst("%document", documentRef.name); 3785 alertString.ReplaceFirst("%app", app.name); 3786 alertString.ReplaceFirst("%symbol", 3787 loaderErrorString.String()); 3788 } else { 3789 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3790 "(Missing symbol: %symbol). \n")); 3791 alertString.ReplaceFirst("%document", documentRef.name); 3792 alertString.ReplaceFirst("%symbol", 3793 loaderErrorString.String()); 3794 } 3795 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3796 } else if (error == B_MISSING_LIBRARY 3797 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { 3798 if (openedDocuments) { 3799 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3800 "with application \"%app\" (Missing libraries: %library). " 3801 "\n")); 3802 alertString.ReplaceFirst("%document", documentRef.name); 3803 alertString.ReplaceFirst("%app", app.name); 3804 alertString.ReplaceFirst("%library", 3805 loaderErrorString.String()); 3806 } else { 3807 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3808 "(Missing libraries: %library). \n")); 3809 alertString.ReplaceFirst("%document", documentRef.name); 3810 alertString.ReplaceFirst("%library", 3811 loaderErrorString.String()); 3812 } 3813 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3814 } else { 3815 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" with " 3816 "application \"%app\" (%error). ")); 3817 alertString.ReplaceFirst("%document", documentRef.name); 3818 alertString.ReplaceFirst("%app", app.name); 3819 alertString.ReplaceFirst("%error", strerror(error)); 3820 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3821 } 3822 } 3823 3824 if (error != B_OK) { 3825 if (openWithOK) { 3826 ASSERT(alternative); 3827 alertString << alternative; 3828 BAlert* alert = new BAlert("", alertString.String(), 3829 B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0, 3830 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3831 alert->SetShortcut(0, B_ESCAPE); 3832 if (alert->Go() == 1) 3833 error = TrackerOpenWith(refs); 3834 } else { 3835 BAlert* alert = new BAlert("", alertString.String(), 3836 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 3837 B_WARNING_ALERT); 3838 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3839 alert->Go(); 3840 } 3841 } 3842 } 3843 3844 // the following three calls don't return any reasonable error codes, 3845 // should fix that, making them void 3846 3847 status_t 3848 TrackerLaunch(const entry_ref* appRef, const BMessage* refs, bool async, 3849 bool openWithOK) 3850 { 3851 if (!async) 3852 _TrackerLaunchAppWithDocuments(appRef, refs, openWithOK); 3853 else { 3854 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs, 3855 openWithOK); 3856 } 3857 3858 return B_OK; 3859 } 3860 3861 status_t 3862 TrackerLaunch(const entry_ref* appRef, bool async) 3863 { 3864 if (!async) 3865 _TrackerLaunchAppWithDocuments(appRef, NULL, false); 3866 else 3867 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false); 3868 3869 return B_OK; 3870 } 3871 3872 status_t 3873 TrackerLaunch(const BMessage* refs, bool async, bool openWithOK) 3874 { 3875 if (!async) 3876 _TrackerLaunchDocuments(NULL, refs, openWithOK); 3877 else 3878 AsynchLaunchBinder(&_TrackerLaunchDocuments, NULL, refs, openWithOK); 3879 3880 return B_OK; 3881 } 3882 3883 3884 // external launch calls; need to be robust, work if Tracker is not running 3885 3886 3887 #if !B_BEOS_VERSION_DANO 3888 _IMPEXP_TRACKER 3889 #endif 3890 status_t 3891 FSLaunchItem(const entry_ref* application, const BMessage* refsReceived, 3892 bool async, bool openWithOK) 3893 { 3894 return TrackerLaunch(application, refsReceived, async, openWithOK); 3895 } 3896 3897 3898 #if !B_BEOS_VERSION_DANO 3899 _IMPEXP_TRACKER 3900 #endif 3901 status_t 3902 FSOpenWith(BMessage* listOfRefs) 3903 { 3904 status_t result = B_ERROR; 3905 listOfRefs->what = B_REFS_RECEIVED; 3906 3907 if (dynamic_cast<TTracker*>(be_app) != NULL) 3908 result = TrackerOpenWith(listOfRefs); 3909 else 3910 ASSERT(!"not yet implemented"); 3911 3912 return result; 3913 } 3914 3915 3916 // legacy calls, need for compatibility 3917 3918 3919 void 3920 FSOpenWithDocuments(const entry_ref* executable, BMessage* documents) 3921 { 3922 TrackerLaunch(executable, documents, true); 3923 delete documents; 3924 } 3925 3926 3927 status_t 3928 FSLaunchUsing(const entry_ref* ref, BMessage* listOfRefs) 3929 { 3930 BMessage temp(B_REFS_RECEIVED); 3931 if (listOfRefs == NULL) { 3932 ASSERT(ref != NULL); 3933 temp.AddRef("refs", ref); 3934 listOfRefs = &temp; 3935 } 3936 FSOpenWith(listOfRefs); 3937 3938 return B_OK; 3939 } 3940 3941 3942 status_t 3943 FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32, bool async) 3944 { 3945 if (refs != NULL) 3946 refs->what = B_REFS_RECEIVED; 3947 3948 status_t result = TrackerLaunch(appRef, refs, async, true); 3949 delete refs; 3950 3951 return result; 3952 } 3953 3954 3955 void 3956 FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32 workspace) 3957 { 3958 FSLaunchItem(appRef, refs, workspace, true); 3959 } 3960 3961 3962 // Get the original path of an entry in the trash 3963 status_t 3964 FSGetOriginalPath(BEntry* entry, BPath* result) 3965 { 3966 status_t err; 3967 entry_ref ref; 3968 err = entry->GetRef(&ref); 3969 if (err != B_OK) 3970 return err; 3971 3972 // Only call the routine for entries in the trash 3973 if (!FSInTrashDir(&ref)) 3974 return B_ERROR; 3975 3976 BNode node(entry); 3977 BString originalPath; 3978 if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) { 3979 // We're in luck, the entry has the original path in an attribute 3980 err = result->SetTo(originalPath.String()); 3981 return err; 3982 } 3983 3984 // Iterate the parent directories to find one with 3985 // the original path attribute 3986 BEntry parent(*entry); 3987 err = parent.InitCheck(); 3988 if (err != B_OK) 3989 return err; 3990 3991 // walk up the directory structure until we find a node 3992 // with original path attribute 3993 do { 3994 // move to the parent of this node 3995 err = parent.GetParent(&parent); 3996 if (err != B_OK) 3997 return err; 3998 3999 // return if we are at the root of the trash 4000 if (FSIsTrashDir(&parent)) 4001 return B_ENTRY_NOT_FOUND; 4002 4003 // get the parent as a node 4004 err = node.SetTo(&parent); 4005 if (err != B_OK) 4006 return err; 4007 } while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK); 4008 4009 // Found the attribute, figure out there this file 4010 // used to live, based on the successfully-read attribute 4011 err = result->SetTo(originalPath.String()); 4012 if (err != B_OK) 4013 return err; 4014 4015 BPath path, pathParent; 4016 err = parent.GetPath(&pathParent); 4017 if (err != B_OK) 4018 return err; 4019 4020 err = entry->GetPath(&path); 4021 if (err != B_OK) 4022 return err; 4023 4024 result->Append(path.Path() + strlen(pathParent.Path()) + 1); 4025 // compute the new path by appending the offset of 4026 // the item we are locating, to the original path 4027 // of the parent 4028 4029 return B_OK; 4030 } 4031 4032 4033 directory_which 4034 WellKnowEntryList::Match(const node_ref* node) 4035 { 4036 const WellKnownEntry* result = MatchEntry(node); 4037 if (result != NULL) 4038 return result->which; 4039 4040 return (directory_which)-1; 4041 } 4042 4043 4044 const WellKnowEntryList::WellKnownEntry* 4045 WellKnowEntryList::MatchEntry(const node_ref* node) 4046 { 4047 if (self == NULL) 4048 self = new WellKnowEntryList(); 4049 4050 return self->MatchEntryCommon(node); 4051 } 4052 4053 4054 const WellKnowEntryList::WellKnownEntry* 4055 WellKnowEntryList::MatchEntryCommon(const node_ref* node) 4056 { 4057 uint32 count = entries.size(); 4058 for (uint32 index = 0; index < count; index++) { 4059 if (*node == entries[index].node) 4060 return &entries[index]; 4061 } 4062 4063 return NULL; 4064 } 4065 4066 4067 void 4068 WellKnowEntryList::Quit() 4069 { 4070 delete self; 4071 self = NULL; 4072 } 4073 4074 4075 void 4076 WellKnowEntryList::AddOne(directory_which which, const char* name) 4077 { 4078 BPath path; 4079 if (find_directory(which, &path, true) != B_OK) 4080 return; 4081 4082 BEntry entry(path.Path(), true); 4083 node_ref node; 4084 if (entry.GetNodeRef(&node) != B_OK) 4085 return; 4086 4087 entries.push_back(WellKnownEntry(&node, which, name)); 4088 } 4089 4090 4091 void 4092 WellKnowEntryList::AddOne(directory_which which, directory_which base, 4093 const char* extra, const char* name) 4094 { 4095 BPath path; 4096 if (find_directory(base, &path, true) != B_OK) 4097 return; 4098 4099 path.Append(extra); 4100 BEntry entry(path.Path(), true); 4101 node_ref node; 4102 if (entry.GetNodeRef(&node) != B_OK) 4103 return; 4104 4105 entries.push_back(WellKnownEntry(&node, which, name)); 4106 } 4107 4108 4109 void 4110 WellKnowEntryList::AddOne(directory_which which, const char* path, 4111 const char* name) 4112 { 4113 BEntry entry(path, true); 4114 node_ref node; 4115 if (entry.GetNodeRef(&node) != B_OK) 4116 return; 4117 4118 entries.push_back(WellKnownEntry(&node, which, name)); 4119 } 4120 4121 4122 WellKnowEntryList::WellKnowEntryList() 4123 { 4124 AddOne(B_SYSTEM_DIRECTORY, "system"); 4125 AddOne((directory_which)B_BOOT_DISK, "/boot", "boot"); 4126 AddOne(B_USER_DIRECTORY, "home"); 4127 4128 AddOne(B_BEOS_FONTS_DIRECTORY, "fonts"); 4129 AddOne(B_USER_FONTS_DIRECTORY, "fonts"); 4130 4131 AddOne(B_BEOS_APPS_DIRECTORY, "apps"); 4132 AddOne(B_APPS_DIRECTORY, "apps"); 4133 AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY, 4134 B_USER_DESKBAR_DIRECTORY, "Applications", "apps"); 4135 4136 AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences"); 4137 AddOne(B_PREFERENCES_DIRECTORY, "preferences"); 4138 AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY, 4139 B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences"); 4140 4141 AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail", 4142 "mail"); 4143 4144 AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY, 4145 "queries", "queries"); 4146 4147 AddOne(B_SYSTEM_DEVELOP_DIRECTORY, "develop"); 4148 AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY, 4149 B_USER_DESKBAR_DIRECTORY, "Development", "develop"); 4150 4151 AddOne(B_USER_CONFIG_DIRECTORY, "config"); 4152 4153 AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY, 4154 "people", "people"); 4155 4156 AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY, 4157 "downloads", "downloads"); 4158 } 4159 4160 WellKnowEntryList* WellKnowEntryList::self = NULL; 4161 4162 } // namespace BPrivate 4163