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