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