1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 22 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN 23 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered 30 trademarks of Be Incorporated in the United States and other countries. Other 31 brand product names are registered trademarks or trademarks of their 32 respective holders. All rights reserved. 33 */ 34 35 // Tracker file system calls. 36 37 // APIs/code in FSUtils.h and FSUtils.cpp is slated for a major cleanup -- in 38 // other words, you will find a lot of ugly cruft in here 39 40 // ToDo: 41 // Move most of preflight error checks to the Model level and only keep those 42 // that have to do with size, reading/writing and name collisions. 43 // Get rid of all the BList based APIs, use BObjectLists. 44 // Clean up the error handling, push most of the user interaction out of the 45 // low level FS calls. 46 47 48 #include <ctype.h> 49 #include <errno.h> 50 #include <strings.h> 51 #include <unistd.h> 52 53 #include <Alert.h> 54 #include <Application.h> 55 #include <Catalog.h> 56 #include <Debug.h> 57 #include <Directory.h> 58 #include <Entry.h> 59 #include <FindDirectory.h> 60 #include <Locale.h> 61 #include <NodeInfo.h> 62 #include <Path.h> 63 #include <Roster.h> 64 #include <Screen.h> 65 #include <String.h> 66 #include <StringFormat.h> 67 #include <SymLink.h> 68 #include <Volume.h> 69 #include <VolumeRoster.h> 70 71 #include <fs_attr.h> 72 #include <fs_info.h> 73 #include <sys/utsname.h> 74 75 #include <AutoLocker.h> 76 #include <libroot/libroot_private.h> 77 #include <system/syscalls.h> 78 #include <system/syscall_load_image.h> 79 80 #include "Attributes.h" 81 #include "Bitmaps.h" 82 #include "Commands.h" 83 #include "FindPanel.h" 84 #include "FSUndoRedo.h" 85 #include "FSUtils.h" 86 #include "InfoWindow.h" 87 #include "MimeTypes.h" 88 #include "OverrideAlert.h" 89 #include "StatusWindow.h" 90 #include "Thread.h" 91 #include "Tracker.h" 92 #include "TrackerSettings.h" 93 #include "Utilities.h" 94 #include "VirtualDirectoryManager.h" 95 96 97 enum { 98 kUserCanceled = B_ERRORS_END + 1, 99 kCopyCanceled = kUserCanceled, 100 kTrashCanceled 101 }; 102 103 enum ConflictCheckResult { 104 kCanceled = kUserCanceled, 105 kPrompt, 106 kSkipAll, 107 kReplace, 108 kReplaceAll, 109 kNoConflicts 110 }; 111 112 113 namespace BPrivate { 114 115 #undef B_TRANSLATION_CONTEXT 116 #define B_TRANSLATION_CONTEXT "FSUtils" 117 118 static status_t FSDeleteFolder(BEntry*, CopyLoopControl*, bool updateStatus, 119 bool deleteTopDir = true, bool upateFileNameInStatus = false); 120 static status_t MoveEntryToTrash(BEntry*, BPoint*, Undo &undo); 121 static void LowLevelCopy(BEntry*, StatStruct*, BDirectory*, char* destName, 122 CopyLoopControl*, BPoint*); 123 status_t DuplicateTask(BObjectList<entry_ref>* srcList); 124 static status_t MoveTask(BObjectList<entry_ref>*, BEntry*, BList*, uint32); 125 static status_t _DeleteTask(BObjectList<entry_ref>*, bool); 126 static status_t _RestoreTask(BObjectList<entry_ref>*); 127 status_t CalcItemsAndSize(CopyLoopControl* loopControl, 128 BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount, 129 off_t* totalSize); 130 status_t MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc, 131 uint32 moveMode, const char* newName, Undo &undo, 132 CopyLoopControl* loopControl); 133 ConflictCheckResult PreFlightNameCheck(BObjectList<entry_ref>* srcList, 134 const BDirectory* destDir, int32* collisionCount, uint32 moveMode); 135 status_t CheckName(uint32 moveMode, const BEntry* srcEntry, 136 const BDirectory* destDir, bool multipleCollisions, 137 ConflictCheckResult &); 138 void CopyAttributes(CopyLoopControl* control, BNode* srcNode, 139 BNode* destNode, void* buffer, size_t bufsize); 140 void CopyPoseLocation(BNode* src, BNode* dest); 141 bool DirectoryMatchesOrContains(const BEntry*, directory_which); 142 bool DirectoryMatchesOrContains(const BEntry*, const char* additionalPath, 143 directory_which); 144 bool DirectoryMatches(const BEntry*, directory_which); 145 bool DirectoryMatches(const BEntry*, const char* additionalPath, 146 directory_which); 147 148 status_t empty_trash(void*); 149 150 151 static const char* kDeleteConfirmationStr = 152 B_TRANSLATE_MARK("Are you sure you want to delete the " 153 "selected item(s)? This operation cannot be reverted."); 154 155 static const char* kReplaceStr = 156 B_TRANSLATE_MARK("You are trying to replace the item:\n" 157 "\t%name%dest\n" 158 "with:\n" 159 "\t%name%src\n\n" 160 "Would you like to replace it with the one you are %movemode?"); 161 162 static const char* kDirectoryReplaceStr = 163 B_TRANSLATE_MARK("An item named \"%name\" already exists in " 164 "this folder, and may contain\nitems with the same names. Would you like " 165 "to replace them with those contained in the folder you are %verb?"); 166 167 static const char* kSymLinkReplaceStr = 168 B_TRANSLATE_MARK("An item named \"%name\" already exists in this " 169 "folder. Would you like to replace it with the symbolic link you are " 170 "creating?"); 171 172 static const char* kNoFreeSpace = 173 B_TRANSLATE_MARK("Sorry, there is not enough free space on the " 174 "destination volume to copy the selection."); 175 176 static const char* kFileErrorString = 177 B_TRANSLATE_MARK("Error copying file \"%name\":\n\t%error\n\n" 178 "Would you like to continue?"); 179 180 static const char* kFolderErrorString = 181 B_TRANSLATE_MARK("Error copying folder \"%name\":\n\t%error\n\n" 182 "Would you like to continue?"); 183 184 static const char* kFileDeleteErrorString = 185 B_TRANSLATE_MARK("There was an error deleting \"%name\"" 186 ":\n\t%error"); 187 188 static const char* kReplaceManyStr = 189 B_TRANSLATE_MARK("Some items already exist in this folder with " 190 "the same names as the items you are %verb.\n \nWould you like to " 191 "replace them with the ones you are %verb or be prompted for each " 192 "one?"); 193 194 static const char* kFindAlternativeStr = 195 B_TRANSLATE_MARK("Would you like to find some other suitable " 196 "application?"); 197 198 static const char* kFindApplicationStr = 199 B_TRANSLATE_MARK("Would you like to find a suitable application " 200 "to open the file?"); 201 202 203 // Skip these attributes when copying in Tracker 204 const char* kSkipAttributes[] = { 205 kAttrPoseInfo, 206 NULL 207 }; 208 209 210 // #pragma mark - CopyLoopControl 211 212 213 CopyLoopControl::~CopyLoopControl() 214 { 215 } 216 217 218 void 219 CopyLoopControl::Init(uint32 jobKind) 220 { 221 } 222 223 224 void 225 CopyLoopControl::Init(int32 totalItems, off_t totalSize, 226 const entry_ref* destDir, bool showCount) 227 { 228 } 229 230 231 bool 232 CopyLoopControl::FileError(const char* message, const char* name, 233 status_t error, bool allowContinue) 234 { 235 return false; 236 } 237 238 239 void 240 CopyLoopControl::UpdateStatus(const char* name, const entry_ref& ref, 241 int32 count, bool optional) 242 { 243 } 244 245 246 bool 247 CopyLoopControl::CheckUserCanceled() 248 { 249 return false; 250 } 251 252 253 CopyLoopControl::OverwriteMode 254 CopyLoopControl::OverwriteOnConflict(const BEntry* srcEntry, 255 const char* destName, const BDirectory* destDir, bool srcIsDir, 256 bool dstIsDir) 257 { 258 return kReplace; 259 } 260 261 262 bool 263 CopyLoopControl::SkipEntry(const BEntry*, bool) 264 { 265 // Tracker makes no exceptions 266 return false; 267 } 268 269 270 void 271 CopyLoopControl::ChecksumChunk(const char*, size_t) 272 { 273 } 274 275 276 bool 277 CopyLoopControl::ChecksumFile(const entry_ref*) 278 { 279 return true; 280 } 281 282 283 bool 284 CopyLoopControl::SkipAttribute(const char*) 285 { 286 return false; 287 } 288 289 290 bool 291 CopyLoopControl::PreserveAttribute(const char*) 292 { 293 return false; 294 } 295 296 297 // #pragma mark - TrackerCopyLoopControl 298 299 300 TrackerCopyLoopControl::TrackerCopyLoopControl() 301 : 302 fThread(find_thread(NULL)), 303 fSourceList(NULL) 304 { 305 } 306 307 308 TrackerCopyLoopControl::TrackerCopyLoopControl(uint32 jobKind) 309 : 310 fThread(find_thread(NULL)), 311 fSourceList(NULL) 312 { 313 Init(jobKind); 314 } 315 316 317 TrackerCopyLoopControl::TrackerCopyLoopControl(int32 totalItems, 318 off_t totalSize) 319 : 320 fThread(find_thread(NULL)), 321 fSourceList(NULL) 322 { 323 Init(totalItems, totalSize); 324 } 325 326 327 TrackerCopyLoopControl::~TrackerCopyLoopControl() 328 { 329 if (gStatusWindow != NULL) 330 gStatusWindow->RemoveStatusItem(fThread); 331 } 332 333 334 void 335 TrackerCopyLoopControl::Init(uint32 jobKind) 336 { 337 if (gStatusWindow != NULL) 338 gStatusWindow->CreateStatusItem(fThread, (StatusWindowState)jobKind); 339 } 340 341 342 void 343 TrackerCopyLoopControl::Init(int32 totalItems, off_t totalSize, 344 const entry_ref* destDir, bool showCount) 345 { 346 if (gStatusWindow != NULL) { 347 gStatusWindow->InitStatusItem(fThread, totalItems, totalSize, 348 destDir, showCount); 349 } 350 } 351 352 353 bool 354 TrackerCopyLoopControl::FileError(const char* message, const char* name, 355 status_t error, bool allowContinue) 356 { 357 BString buffer(message); 358 buffer.ReplaceFirst("%name", name); 359 buffer.ReplaceFirst("%error", strerror(error)); 360 361 if (allowContinue) { 362 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"), 363 B_TRANSLATE("OK"), 0, B_WIDTH_AS_USUAL, B_STOP_ALERT); 364 alert->SetShortcut(0, B_ESCAPE); 365 return alert->Go() != 0; 366 } 367 368 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"), 0, 0, 369 B_WIDTH_AS_USUAL, B_STOP_ALERT); 370 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 371 alert->Go(); 372 return false; 373 } 374 375 376 void 377 TrackerCopyLoopControl::UpdateStatus(const char* name, const entry_ref&, 378 int32 count, bool optional) 379 { 380 if (gStatusWindow != NULL) 381 gStatusWindow->UpdateStatus(fThread, name, count, optional); 382 } 383 384 385 bool 386 TrackerCopyLoopControl::CheckUserCanceled() 387 { 388 if (gStatusWindow == NULL) 389 return false; 390 391 if (gStatusWindow->CheckCanceledOrPaused(fThread)) 392 return true; 393 394 if (fSourceList != NULL) { 395 // TODO: Check if the user dropped additional files onto this job. 396 // printf("%p->CheckUserCanceled()\n", this); 397 } 398 399 return false; 400 } 401 402 403 bool 404 TrackerCopyLoopControl::SkipAttribute(const char* attributeName) 405 { 406 for (const char** skipAttribute = kSkipAttributes; *skipAttribute; 407 skipAttribute++) { 408 if (strcmp(*skipAttribute, attributeName) == 0) 409 return true; 410 } 411 412 return false; 413 } 414 415 416 void 417 TrackerCopyLoopControl::SetSourceList(EntryList* list) 418 { 419 fSourceList = list; 420 } 421 422 423 // #pragma mark - the rest 424 425 426 static BNode* 427 GetWritableNode(BEntry* entry, StatStruct* statBuf = 0) 428 { 429 // utility call that works around the problem with BNodes not being 430 // universally writeable 431 // BNodes created on files will fail to WriteAttr because they do not 432 // have the right r/w permissions 433 434 StatStruct localStatbuf; 435 436 if (!statBuf) { 437 statBuf = &localStatbuf; 438 if (entry->GetStat(statBuf) != B_OK) 439 return 0; 440 } 441 442 if (S_ISREG(statBuf->st_mode)) 443 return new BFile(entry, O_RDWR); 444 445 return new BNode(entry); 446 } 447 448 449 bool 450 CheckDevicesEqual(const entry_ref* srcRef, const Model* targetModel) 451 { 452 BDirectory destDir (targetModel->EntryRef()); 453 struct stat deststat; 454 destDir.GetStat(&deststat); 455 456 return srcRef->device == deststat.st_dev; 457 } 458 459 460 status_t 461 FSSetPoseLocation(ino_t destDirInode, BNode* destNode, BPoint point) 462 { 463 PoseInfo poseInfo; 464 poseInfo.fInvisible = false; 465 poseInfo.fInitedDirectory = destDirInode; 466 poseInfo.fLocation = point; 467 468 status_t result = destNode->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, 469 &poseInfo, sizeof(poseInfo)); 470 471 if (result == sizeof(poseInfo)) 472 return B_OK; 473 474 return result; 475 } 476 477 478 status_t 479 FSSetPoseLocation(BEntry* entry, BPoint point) 480 { 481 BNode node(entry); 482 status_t result = node.InitCheck(); 483 if (result != B_OK) 484 return result; 485 486 BDirectory parent; 487 result = entry->GetParent(&parent); 488 if (result != B_OK) 489 return result; 490 491 node_ref destNodeRef; 492 result = parent.GetNodeRef(&destNodeRef); 493 if (result != B_OK) 494 return result; 495 496 return FSSetPoseLocation(destNodeRef.node, &node, point); 497 } 498 499 500 bool 501 FSGetPoseLocation(const BNode* node, BPoint* point) 502 { 503 PoseInfo poseInfo; 504 if (ReadAttr(node, kAttrPoseInfo, kAttrPoseInfoForeign, 505 B_RAW_TYPE, 0, &poseInfo, sizeof(poseInfo), &PoseInfo::EndianSwap) 506 == kReadAttrFailed) { 507 return false; 508 } 509 510 if (poseInfo.fInitedDirectory == -1LL) 511 return false; 512 513 *point = poseInfo.fLocation; 514 515 return true; 516 } 517 518 519 static void 520 SetupPoseLocation(ino_t sourceParentIno, ino_t destParentIno, 521 const BNode* sourceNode, BNode* destNode, BPoint* loc) 522 { 523 BPoint point; 524 if (loc == NULL 525 // we don't have a position yet 526 && sourceParentIno != destParentIno 527 // we aren't copying into the same directory 528 && FSGetPoseLocation(sourceNode, &point)) { 529 // the original has a valid inited location 530 loc = &point; 531 // copy the originals location 532 } 533 534 if (loc != NULL && loc != (BPoint*)-1) { 535 // loc of -1 is used when copying/moving into a window in list mode 536 // where copying positions would not work 537 // ToSo: 538 // should push all this logic to upper levels 539 FSSetPoseLocation(destParentIno, destNode, *loc); 540 } 541 } 542 543 544 void 545 FSMoveToFolder(BObjectList<entry_ref>* srcList, BEntry* destEntry, 546 uint32 moveMode, BList* pointList) 547 { 548 if (srcList->IsEmpty()) { 549 delete srcList; 550 delete pointList; 551 delete destEntry; 552 return; 553 } 554 555 LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList, 556 destEntry, pointList, moveMode); 557 } 558 559 560 void 561 FSDelete(entry_ref* ref, bool async, bool confirm) 562 { 563 BObjectList<entry_ref>* list = new BObjectList<entry_ref>(1, true); 564 list->AddItem(ref); 565 FSDeleteRefList(list, async, confirm); 566 } 567 568 569 void 570 FSDeleteRefList(BObjectList<entry_ref>* list, bool async, bool confirm) 571 { 572 if (async) { 573 LaunchInNewThread("DeleteTask", B_NORMAL_PRIORITY, _DeleteTask, list, 574 confirm); 575 } else 576 _DeleteTask(list, confirm); 577 } 578 579 580 void 581 FSRestoreRefList(BObjectList<entry_ref>* list, bool async) 582 { 583 if (async) { 584 LaunchInNewThread("RestoreTask", B_NORMAL_PRIORITY, _RestoreTask, 585 list); 586 } else 587 _RestoreTask(list); 588 } 589 590 591 void 592 FSMoveToTrash(BObjectList<entry_ref>* srcList, BList* pointList, bool async) 593 { 594 if (srcList->IsEmpty()) { 595 delete srcList; 596 delete pointList; 597 return; 598 } 599 600 if (async) 601 LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList, 602 (BEntry*)0, pointList, kMoveSelectionTo); 603 else 604 MoveTask(srcList, 0, pointList, kMoveSelectionTo); 605 } 606 607 608 enum { 609 kNotConfirmed, 610 kConfirmedHomeMove, 611 kConfirmedAll 612 }; 613 614 615 bool 616 ConfirmChangeIfWellKnownDirectory(const BEntry* entry, DestructiveAction action, 617 bool dontAsk, int32* confirmedAlready) 618 { 619 // Don't let the user casually move/change important files/folders 620 // 621 // This is a cheap replacement for having a real UID support turned 622 // on and not running as root all the time 623 624 if (confirmedAlready && *confirmedAlready == kConfirmedAll) 625 return true; 626 627 if (FSIsDeskDir(entry) || FSIsTrashDir(entry) || FSIsRootDir(entry)) 628 return false; 629 630 if ((!DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY) 631 && !DirectoryMatchesOrContains(entry, B_USER_DIRECTORY)) 632 || DirectoryMatchesOrContains(entry, B_SYSTEM_TEMP_DIRECTORY)) 633 // quick way out 634 return true; 635 636 BString warning; 637 bool requireOverride = true; 638 639 if (DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY)) { 640 if (action == kRename) { 641 warning.SetTo( 642 B_TRANSLATE("If you rename the system folder or its " 643 "contents, you won't be able to boot %osName!\n\nAre you sure " 644 "you want to do this?\n\nTo rename the system folder or its " 645 "contents anyway, hold down the Shift key and click " 646 "\"Rename\".")); 647 } else if(action == kMove) { 648 warning.SetTo( 649 B_TRANSLATE("If you move the system folder or its " 650 "contents, you won't be able to boot %osName!\n\nAre you sure " 651 "you want to do this?\n\nTo move the system folder or its " 652 "contents anyway, hold down the Shift key and click " 653 "\"Move\".")); 654 } else { 655 warning.SetTo( 656 B_TRANSLATE("If you alter the system folder or its " 657 "contents, you won't be able to boot %osName!\n\nAre you sure " 658 "you want to do this?\n\nTo alter the system folder or its " 659 "contents anyway, hold down the Shift key and click " 660 "\"I know what I'm doing\".")); 661 } 662 } else if (DirectoryMatches(entry, B_USER_DIRECTORY)) { 663 if (action == kRename) { 664 warning .SetTo( 665 B_TRANSLATE("If you rename the home folder, %osName " 666 "may not behave properly!\n\nAre you sure you want to do this?" 667 "\n\nTo rename the home folder anyway, hold down the " 668 "Shift key and click \"Rename\".")); 669 } else if (action == kMove) { 670 warning .SetTo( 671 B_TRANSLATE("If you move the home folder, %osName " 672 "may not behave properly!\n\nAre you sure you want to do this?" 673 "\n\nTo move the home folder anyway, hold down the " 674 "Shift key and click \"Move\".")); 675 } else { 676 warning .SetTo( 677 B_TRANSLATE("If you alter the home folder, %osName " 678 "may not behave properly!\n\nAre you sure you want to do this?" 679 "\n\nTo alter the home folder anyway, hold down the " 680 "Shift key and click \"I know what I'm doing\".")); 681 } 682 } else if (DirectoryMatchesOrContains(entry, B_USER_CONFIG_DIRECTORY) 683 || DirectoryMatchesOrContains(entry, B_SYSTEM_SETTINGS_DIRECTORY)) { 684 if (action == kRename) { 685 warning.SetTo( 686 B_TRANSLATE("If you rename %target, %osName may not behave " 687 "properly!\n\nAre you sure you want to do this?")); 688 } else if (action == kMove) { 689 warning.SetTo( 690 B_TRANSLATE("If you move %target, %osName may not behave " 691 "properly!\n\nAre you sure you want to do this?")); 692 } else { 693 warning.SetTo( 694 B_TRANSLATE("If you alter %target, %osName may not behave " 695 "properly!\n\nAre you sure you want to do this?")); 696 } 697 698 if (DirectoryMatchesOrContains(entry, "beos_mime", 699 B_USER_SETTINGS_DIRECTORY) 700 || DirectoryMatchesOrContains(entry, "beos_mime", 701 B_SYSTEM_SETTINGS_DIRECTORY)) { 702 warning.ReplaceFirst("%target", B_TRANSLATE("the MIME settings")); 703 requireOverride = false; 704 } else if (DirectoryMatches(entry, B_USER_CONFIG_DIRECTORY)) { 705 warning.ReplaceFirst("%target", B_TRANSLATE("the config folder")); 706 requireOverride = false; 707 } else if (DirectoryMatches(entry, B_USER_SETTINGS_DIRECTORY) 708 || DirectoryMatches(entry, B_SYSTEM_SETTINGS_DIRECTORY)) { 709 warning.ReplaceFirst("%target", B_TRANSLATE("the settings folder")); 710 requireOverride = false; 711 } else { 712 // It was not a special directory/file after all. Allow renaming. 713 return true; 714 } 715 } else 716 return true; 717 718 if (dontAsk) 719 return false; 720 721 if (confirmedAlready && *confirmedAlready == kConfirmedHomeMove 722 && !requireOverride) 723 // we already warned about moving home this time around 724 return true; 725 726 struct utsname name; 727 if (uname(&name) == -1) 728 warning.ReplaceFirst("%osName", "Haiku"); 729 else 730 warning.ReplaceFirst("%osName", name.sysname); 731 732 BString buttonLabel; 733 if (action == kRename) { 734 buttonLabel = B_TRANSLATE_COMMENT("Rename", "button label"); 735 } else if (action == kMove) { 736 buttonLabel = B_TRANSLATE_COMMENT("Move", "button label"); 737 } else { 738 buttonLabel = B_TRANSLATE_COMMENT("I know what I'm doing", 739 "button label"); 740 } 741 742 OverrideAlert* alert = new OverrideAlert("", warning.String(), 743 buttonLabel.String(), (requireOverride ? B_SHIFT_KEY : 0), 744 B_TRANSLATE("Cancel"), 0, NULL, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 745 alert->SetShortcut(1, B_ESCAPE); 746 if (alert->Go() == 1) { 747 if (confirmedAlready) 748 *confirmedAlready = kNotConfirmed; 749 return false; 750 } 751 752 if (confirmedAlready) { 753 if (!requireOverride) 754 *confirmedAlready = kConfirmedHomeMove; 755 else 756 *confirmedAlready = kConfirmedAll; 757 } 758 759 return true; 760 } 761 762 763 status_t 764 EditModelName(const Model* model, const char* name, size_t length) 765 { 766 if (model == NULL || name == NULL || name[0] == '\0' || length <= 0) 767 return B_BAD_VALUE; 768 769 BEntry entry(model->EntryRef()); 770 status_t result = entry.InitCheck(); 771 if (result != B_OK) 772 return result; 773 774 // TODO: use model-flavor specific virtuals for these special renamings 775 776 if (model->HasLocalizedName() || model->IsDesktop() || model->IsRoot() 777 || model->IsTrash() || model->IsVirtualDirectory()) { 778 result = B_NOT_ALLOWED; 779 } else if (model->IsQuery()) { 780 // write to query parameter 781 BModelWriteOpener opener(const_cast<Model*>(model)); 782 ASSERT(model->Node()); 783 MoreOptionsStruct::SetQueryTemporary(model->Node(), false); 784 785 RenameUndo undo(entry, name); 786 result = entry.Rename(name); 787 if (result != B_OK) 788 undo.Remove(); 789 } else if (model->IsVolume()) { 790 // write volume name 791 BVolume volume(model->NodeRef()->device); 792 result = volume.InitCheck(); 793 if (result == B_OK && volume.IsReadOnly()) 794 result = B_READ_ONLY_DEVICE; 795 if (result == B_OK) { 796 RenameVolumeUndo undo(volume, name); 797 result = volume.SetName(name); 798 if (result != B_OK) 799 undo.Remove(); 800 } 801 } else { 802 BVolume volume(model->NodeRef()->device); 803 result = volume.InitCheck(); 804 if (result == B_OK && volume.IsReadOnly()) 805 result = B_READ_ONLY_DEVICE; 806 if (result == B_OK) 807 result = ShouldEditRefName(model->EntryRef(), name, length); 808 if (result == B_OK) { 809 RenameUndo undo(entry, name); 810 result = entry.Rename(name); 811 if (result != B_OK) 812 undo.Remove(); 813 } 814 } 815 816 return result; 817 } 818 819 820 status_t 821 ShouldEditRefName(const entry_ref* ref, const char* name, size_t length) 822 { 823 if (ref == NULL || name == NULL || name[0] == '\0' || length <= 0) 824 return B_BAD_VALUE; 825 826 BEntry entry(ref); 827 if (entry.InitCheck() != B_OK) 828 return B_NO_INIT; 829 830 // check if name is too long 831 if (length >= B_FILE_NAME_LENGTH) { 832 BString text; 833 if (entry.IsDirectory()) 834 text = B_TRANSLATE("The entered folder name is too long."); 835 else 836 text = B_TRANSLATE("The entered file name is too long."); 837 838 BAlert* alert = new BAlert("", text, B_TRANSLATE("OK"), 839 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 840 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 841 alert->Go(); 842 843 return B_NAME_TOO_LONG; 844 } 845 846 // same name 847 if (strcmp(name, ref->name) == 0) 848 return B_OK; 849 850 // user declined rename in system directory 851 if (!ConfirmChangeIfWellKnownDirectory(&entry, kRename)) 852 return B_CANCELED; 853 854 // entry must have a parent directory 855 BDirectory parent; 856 if (entry.GetParent(&parent) != B_OK) 857 return B_ERROR; 858 859 // check for name conflict 860 if (parent.Contains(name)) { 861 BString text(B_TRANSLATE("An item named '%filename%' already exists.")); 862 text.ReplaceFirst("%filename%", name); 863 864 BAlert* alert = new BAlert("", text, B_TRANSLATE("OK"), 865 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 866 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 867 alert->Go(); 868 869 return B_NAME_IN_USE; 870 } 871 872 // success 873 return B_OK; 874 } 875 876 877 static status_t 878 InitCopy(CopyLoopControl* loopControl, uint32 moveMode, 879 BObjectList<entry_ref>* srcList, BVolume* dstVol, BDirectory* destDir, 880 entry_ref* destRef, bool preflightNameCheck, bool needSizeCalculation, 881 int32* collisionCount, ConflictCheckResult* preflightResult) 882 { 883 if (dstVol->IsReadOnly()) 884 return B_READ_ONLY_DEVICE; 885 886 int32 numItems = srcList->CountItems(); 887 int32 askOnceOnly = kNotConfirmed; 888 for (int32 index = 0; index < numItems; index++) { 889 // we could check for this while iterating through items in each of 890 // the copy loops, except it takes forever to call CalcItemsAndSize 891 BEntry entry((entry_ref*)srcList->ItemAt(index)); 892 if (FSIsRootDir(&entry)) { 893 BString errorStr; 894 if (moveMode == kCreateLink) { 895 errorStr.SetTo( 896 B_TRANSLATE("You cannot create a link to the root " 897 "directory.")); 898 } else { 899 errorStr.SetTo( 900 B_TRANSLATE("You cannot copy or move the root " 901 "directory.")); 902 } 903 904 BAlert* alert = new BAlert("", errorStr.String(), 905 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 906 B_WARNING_ALERT); 907 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 908 alert->Go(); 909 return B_ERROR; 910 } 911 if (moveMode == kMoveSelectionTo 912 && !ConfirmChangeIfWellKnownDirectory(&entry, kMove, 913 false, &askOnceOnly)) { 914 return B_ERROR; 915 } 916 } 917 918 if (preflightNameCheck) { 919 ASSERT(collisionCount); 920 ASSERT(preflightResult); 921 922 *preflightResult = kPrompt; 923 *collisionCount = 0; 924 925 *preflightResult = PreFlightNameCheck(srcList, destDir, 926 collisionCount, moveMode); 927 if (*preflightResult == kCanceled) { 928 // user canceled 929 return B_ERROR; 930 } 931 } 932 933 // set up the status display 934 switch (moveMode) { 935 case kCopySelectionTo: 936 case kDuplicateSelection: 937 case kMoveSelectionTo: 938 { 939 loopControl->Init(moveMode == kMoveSelectionTo ? kMoveState 940 : kCopyState); 941 942 int32 totalItems = 0; 943 off_t totalSize = 0; 944 if (needSizeCalculation) { 945 if (CalcItemsAndSize(loopControl, srcList, 946 dstVol->BlockSize(), &totalItems, &totalSize) 947 != B_OK) { 948 return B_ERROR; 949 } 950 951 // check for free space before starting copy 952 if ((totalSize + (4* kKBSize)) >= dstVol->FreeBytes()) { 953 BAlert* alert = new BAlert("", 954 B_TRANSLATE_NOCOLLECT(kNoFreeSpace), 955 B_TRANSLATE("Cancel"), 956 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 957 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 958 alert->Go(); 959 return B_ERROR; 960 } 961 } 962 963 loopControl->Init(totalItems, totalSize, destRef); 964 break; 965 } 966 967 case kCreateLink: 968 if (numItems > 10) { 969 // this will be fast, only put up status if lots of items 970 // moved, links created 971 loopControl->Init(kCreateLinkState); 972 loopControl->Init(numItems, numItems, destRef); 973 } 974 break; 975 } 976 977 return B_OK; 978 } 979 980 981 // ToDo: 982 // get rid of this cruft 983 bool 984 delete_ref(void* ref) 985 { 986 delete (entry_ref*)ref; 987 return false; 988 } 989 990 991 bool 992 delete_point(void* point) 993 { 994 delete (BPoint*)point; 995 return false; 996 } 997 998 999 static status_t 1000 MoveTask(BObjectList<entry_ref>* srcList, BEntry* destEntry, BList* pointList, 1001 uint32 moveMode) 1002 { 1003 ASSERT(!srcList->IsEmpty()); 1004 1005 // extract information from src, dest models 1006 // ## note that we're assuming all items come from the same volume 1007 // ## by looking only at FirstItem here which is not a good idea 1008 dev_t srcVolumeDevice = srcList->FirstItem()->device; 1009 dev_t destVolumeDevice = srcVolumeDevice; 1010 1011 StatStruct deststat; 1012 BVolume volume(srcVolumeDevice); 1013 entry_ref destRef; 1014 1015 bool destIsTrash = false; 1016 BDirectory destDir; 1017 BDirectory* destDirToCheck = NULL; 1018 bool needPreflightNameCheck = false; 1019 bool sourceIsReadOnly = volume.IsReadOnly(); 1020 volume.Unset(); 1021 1022 bool fromUndo = FSIsUndoMoveMode(moveMode); 1023 moveMode = FSMoveMode(moveMode); 1024 1025 // if we're not passed a destEntry then we are supposed to move to trash 1026 if (destEntry != NULL) { 1027 destEntry->GetRef(&destRef); 1028 1029 destDir.SetTo(destEntry); 1030 destDir.GetStat(&deststat); 1031 destDirToCheck = &destDir; 1032 1033 destVolumeDevice = deststat.st_dev; 1034 destIsTrash = FSIsTrashDir(destEntry); 1035 volume.SetTo(destVolumeDevice); 1036 1037 needPreflightNameCheck = true; 1038 } else if (moveMode == kDuplicateSelection) { 1039 BEntry entry; 1040 entry.SetTo(srcList->FirstItem()); 1041 entry.GetParent(&destDir); 1042 volume.SetTo(srcVolumeDevice); 1043 } else { 1044 // move is to trash 1045 destIsTrash = true; 1046 1047 FSGetTrashDir(&destDir, srcVolumeDevice); 1048 volume.SetTo(srcVolumeDevice); 1049 1050 BEntry entry; 1051 destDir.GetEntry(&entry); 1052 destDirToCheck = &destDir; 1053 1054 entry.GetRef(&destRef); 1055 } 1056 1057 // change the move mode if needed 1058 if (moveMode == kCopySelectionTo && destIsTrash) { 1059 // cannot copy to trash 1060 moveMode = kMoveSelectionTo; 1061 } 1062 1063 if (moveMode == kMoveSelectionTo && sourceIsReadOnly) 1064 moveMode = kCopySelectionTo; 1065 1066 bool needSizeCalculation = true; 1067 if ((moveMode == kMoveSelectionTo && srcVolumeDevice == destVolumeDevice) 1068 || destIsTrash) { 1069 needSizeCalculation = false; 1070 } 1071 1072 // we need the undo object later on, so we create it no matter 1073 // if we really need it or not (it's very lightweight) 1074 MoveCopyUndo undo(srcList, destDir, pointList, moveMode); 1075 if (fromUndo) 1076 undo.Remove(); 1077 1078 TrackerCopyLoopControl loopControl; 1079 1080 ConflictCheckResult conflictCheckResult = kPrompt; 1081 int32 collisionCount = 0; 1082 // TODO: Status item is created in InitCopy(), but it would be kind of 1083 // neat to move all that into TrackerCopyLoopControl 1084 status_t result = InitCopy(&loopControl, moveMode, srcList, 1085 &volume, destDirToCheck, &destRef, needPreflightNameCheck, 1086 needSizeCalculation, &collisionCount, &conflictCheckResult); 1087 1088 loopControl.SetSourceList(srcList); 1089 1090 if (result == B_OK) { 1091 for (int32 i = 0; i < srcList->CountItems(); i++) { 1092 BPoint* loc = (BPoint*)-1; 1093 // a loc of -1 forces autoplacement, rather than copying the 1094 // position of the original node 1095 // TODO: 1096 // Clean this mess up! 1097 // What could be a cleaner design is to pass along some kind 1098 // "filter" object that post-processes poses, i.e. adds the 1099 // location or other stuff. It should not be a job of the 1100 // copy-engine. 1101 1102 entry_ref* srcRef = srcList->ItemAt(i); 1103 1104 if (moveMode == kDuplicateSelection) { 1105 BEntry entry(srcRef); 1106 entry.GetParent(&destDir); 1107 destDir.GetStat(&deststat); 1108 volume.SetTo(srcRef->device); 1109 } 1110 1111 // handle case where item is dropped into folder it already lives 1112 // in which could happen if dragging from a query window 1113 if (moveMode != kCreateLink 1114 && moveMode != kCreateRelativeLink 1115 && moveMode != kDuplicateSelection 1116 && !destIsTrash 1117 && (srcRef->device == destRef.device 1118 && srcRef->directory == deststat.st_ino)) { 1119 continue; 1120 } 1121 1122 if (loopControl.CheckUserCanceled()) 1123 break; 1124 1125 BEntry sourceEntry(srcRef); 1126 if (sourceEntry.InitCheck() != B_OK) { 1127 BString error(B_TRANSLATE("Error moving \"%name\".")); 1128 error.ReplaceFirst("%name", srcRef->name); 1129 BAlert* alert = new BAlert("", error.String(), 1130 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 1131 B_WARNING_ALERT); 1132 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1133 alert->Go(); 1134 break; 1135 } 1136 1137 // are we moving item to trash? 1138 if (destIsTrash) { 1139 if (pointList != NULL) 1140 loc = (BPoint*)pointList->ItemAt(i); 1141 1142 result = MoveEntryToTrash(&sourceEntry, loc, undo); 1143 if (result != B_OK) { 1144 BString error(B_TRANSLATE("Error moving \"%name\" to Trash. " 1145 "(%error)")); 1146 error.ReplaceFirst("%name", srcRef->name); 1147 error.ReplaceFirst("%error", strerror(result)); 1148 BAlert* alert = new BAlert("", error.String(), 1149 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 1150 B_WARNING_ALERT); 1151 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1152 alert->Go(); 1153 break; 1154 } 1155 continue; 1156 } 1157 1158 // resolve name collisions and hierarchy problems 1159 if (CheckName(moveMode, &sourceEntry, &destDir, 1160 collisionCount > 1, conflictCheckResult) != B_OK) { 1161 // we will skip the current item, because we got a conflict 1162 // and were asked to or because there was some conflict 1163 1164 // update the status because item got skipped and the status 1165 // will not get updated by the move call 1166 loopControl.UpdateStatus(srcRef->name, *srcRef, 1); 1167 1168 continue; 1169 } 1170 1171 // get location to place this item 1172 if (pointList && moveMode != kCopySelectionTo) { 1173 loc = (BPoint*)pointList->ItemAt(i); 1174 1175 BNode* src_node = GetWritableNode(&sourceEntry); 1176 if (src_node && src_node->InitCheck() == B_OK) { 1177 PoseInfo poseInfo; 1178 poseInfo.fInvisible = false; 1179 poseInfo.fInitedDirectory = deststat.st_ino; 1180 poseInfo.fLocation = *loc; 1181 src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, 1182 &poseInfo, sizeof(poseInfo)); 1183 } 1184 delete src_node; 1185 } 1186 1187 if (pointList) 1188 loc = (BPoint*)pointList->ItemAt(i); 1189 1190 result = MoveItem(&sourceEntry, &destDir, loc, moveMode, NULL, 1191 undo, &loopControl); 1192 if (result != B_OK) 1193 break; 1194 } 1195 } 1196 1197 // duplicates of srcList, destFolder were created - dispose them 1198 delete srcList; 1199 delete destEntry; 1200 1201 // delete file location list and all Points within 1202 if (pointList != NULL) { 1203 pointList->DoForEach(delete_point); 1204 delete pointList; 1205 } 1206 1207 return B_OK; 1208 } 1209 1210 1211 class FailWithAlert { 1212 public: 1213 static void FailOnError(status_t error, const char* string, 1214 const char* name = NULL) 1215 { 1216 if (error != B_OK) 1217 throw FailWithAlert(error, string, name); 1218 } 1219 1220 FailWithAlert(status_t error, const char* string, const char* name) 1221 : 1222 fString(string), 1223 fName(name), 1224 fError(error) 1225 { 1226 } 1227 1228 const char* fString; 1229 const char* fName; 1230 status_t fError; 1231 }; 1232 1233 1234 class MoveError { 1235 public: 1236 static void FailOnError(status_t error) 1237 { 1238 if (error != B_OK) 1239 throw MoveError(error); 1240 } 1241 1242 MoveError(status_t error) 1243 : 1244 fError(error) 1245 { 1246 } 1247 1248 status_t fError; 1249 }; 1250 1251 1252 void 1253 CopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir, 1254 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName, 1255 Undo &undo) 1256 { 1257 if (loopControl->SkipEntry(srcFile, true)) 1258 return; 1259 1260 node_ref node; 1261 destDir->GetNodeRef(&node); 1262 BVolume volume(node.device); 1263 1264 // check for free space first 1265 if ((srcStat->st_size + kKBSize) >= volume.FreeBytes()) { 1266 loopControl->FileError(B_TRANSLATE_NOCOLLECT(kNoFreeSpace), "", 1267 B_DEVICE_FULL, false); 1268 throw (status_t)B_DEVICE_FULL; 1269 } 1270 1271 char destName[B_FILE_NAME_LENGTH]; 1272 srcFile->GetName(destName); 1273 entry_ref ref; 1274 srcFile->GetRef(&ref); 1275 1276 loopControl->UpdateStatus(destName, ref, 1024, true); 1277 1278 if (makeOriginalName) { 1279 BString suffix(" "); 1280 suffix << B_TRANSLATE_COMMENT("copy", "filename copy"), 1281 FSMakeOriginalName(destName, destDir, suffix.String()); 1282 undo.UpdateEntry(srcFile, destName); 1283 } 1284 1285 BEntry conflictingEntry; 1286 if (destDir->FindEntry(destName, &conflictingEntry) == B_OK) { 1287 switch (loopControl->OverwriteOnConflict(srcFile, destName, destDir, 1288 false, false)) { 1289 case TrackerCopyLoopControl::kSkip: 1290 // we are about to ignore this entire directory 1291 return; 1292 1293 case TrackerCopyLoopControl::kReplace: 1294 if (!conflictingEntry.IsDirectory()) { 1295 ThrowOnError(conflictingEntry.Remove()); 1296 break; 1297 } 1298 // fall through if not a directory 1299 case TrackerCopyLoopControl::kMerge: 1300 // This flag implies that the attributes should be kept 1301 // on the file. Just ignore it. 1302 break; 1303 } 1304 } 1305 1306 try { 1307 LowLevelCopy(srcFile, srcStat, destDir, destName, loopControl, loc); 1308 } catch (status_t err) { 1309 if (err == kCopyCanceled) 1310 throw (status_t)err; 1311 1312 if (err == B_FILE_EXISTS) { 1313 // A file with the same name was created after BDirectory::FindEntry was called and 1314 // before LowLevelCopy could finish. In a move operation, if the standard file error 1315 // alert were displayed and the user chose to continue, the file that we just failed 1316 // to copy would be lost. Don't offer the option to continue. 1317 BString lowLevelExistsString( 1318 B_TRANSLATE("Error copying file \"%name\":\n\t%error\n\n")); 1319 // The error may have resulted from the user dragging a set of selected files to a 1320 // case-insensitive volume, when 2 files in the set differ only in case. 1321 node_ref destRef; 1322 destDir->GetNodeRef(&destRef); 1323 fs_info destInfo; 1324 _kern_read_fs_info(destRef.device, &destInfo); 1325 if (strcmp(destInfo.fsh_name, "fat") == 0) { 1326 lowLevelExistsString += B_TRANSLATE("Note: file names in the destination file " 1327 "system are not case-sensitive.\n"); 1328 } 1329 loopControl->FileError(lowLevelExistsString.String(), destName, err, false); 1330 throw (status_t)err; 1331 } 1332 1333 if (err != B_OK) { 1334 if (!loopControl->FileError( 1335 B_TRANSLATE_NOCOLLECT(kFileErrorString), destName, err, 1336 true)) { 1337 throw (status_t)err; 1338 } else { 1339 // user selected continue in spite of error, update status bar 1340 loopControl->UpdateStatus(NULL, ref, (int32)srcStat->st_size); 1341 } 1342 } 1343 } 1344 } 1345 1346 1347 #ifdef _SILENTLY_CORRECT_FILE_NAMES 1348 static bool 1349 CreateFileSystemCompatibleName(const BDirectory* destDir, char* destName) 1350 { 1351 // Is it a FAT32 file system? 1352 // (this is the only one we currently know about) 1353 1354 BEntry target; 1355 destDir->GetEntry(&target); 1356 entry_ref targetRef; 1357 fs_info info; 1358 if (target.GetRef(&targetRef) == B_OK 1359 && fs_stat_dev(targetRef.device, &info) == B_OK 1360 && !strcmp(info.fsh_name, "fat")) { 1361 bool wasInvalid = false; 1362 1363 // it's a FAT32 file system, now check the name 1364 1365 int32 length = strlen(destName) - 1; 1366 while (destName[length] == '.') { 1367 // invalid name, just cut off the dot at the end 1368 destName[length--] = '\0'; 1369 wasInvalid = true; 1370 } 1371 1372 char* invalid = destName; 1373 while ((invalid = strpbrk(invalid, "?<>\\:\"|*")) != NULL) { 1374 invalid[0] = '_'; 1375 wasInvalid = true; 1376 } 1377 1378 return wasInvalid; 1379 } 1380 1381 return false; 1382 } 1383 #endif 1384 1385 1386 static void 1387 LowLevelCopy(BEntry* srcEntry, StatStruct* srcStat, BDirectory* destDir, 1388 char* destName, CopyLoopControl* loopControl, BPoint* loc) 1389 { 1390 entry_ref ref; 1391 ThrowOnError(srcEntry->GetRef(&ref)); 1392 1393 if (S_ISLNK(srcStat->st_mode)) { 1394 // handle symbolic links 1395 BSymLink srcLink; 1396 BSymLink newLink; 1397 char linkpath[MAXPATHLEN]; 1398 1399 ThrowOnError(srcLink.SetTo(srcEntry)); 1400 ssize_t size = srcLink.ReadLink(linkpath, MAXPATHLEN - 1); 1401 if (size < 0) 1402 ThrowOnError(size); 1403 ThrowOnError(destDir->CreateSymLink(destName, linkpath, &newLink)); 1404 1405 node_ref destNodeRef; 1406 destDir->GetNodeRef(&destNodeRef); 1407 // copy or write new pose location as a first thing 1408 SetupPoseLocation(ref.directory, destNodeRef.node, &srcLink, 1409 &newLink, loc); 1410 1411 BNodeInfo nodeInfo(&newLink); 1412 nodeInfo.SetType(B_LINK_MIMETYPE); 1413 1414 newLink.SetPermissions(srcStat->st_mode); 1415 newLink.SetOwner(srcStat->st_uid); 1416 newLink.SetGroup(srcStat->st_gid); 1417 newLink.SetModificationTime(srcStat->st_mtime); 1418 newLink.SetCreationTime(srcStat->st_crtime); 1419 1420 return; 1421 } 1422 1423 BFile srcFile(srcEntry, O_RDONLY); 1424 ThrowOnInitCheckError(&srcFile); 1425 1426 const size_t kMinBufferSize = 1024* 128; 1427 const size_t kMaxBufferSize = 1024* 1024; 1428 1429 size_t bufsize = kMinBufferSize; 1430 if ((off_t)bufsize < srcStat->st_size) { 1431 // File bigger than the buffer size: determine an optimal buffer size 1432 system_info sinfo; 1433 get_system_info(&sinfo); 1434 size_t freesize = static_cast<size_t>( 1435 (sinfo.max_pages - sinfo.used_pages) * B_PAGE_SIZE); 1436 bufsize = freesize / 4; 1437 // take 1/4 of RAM max 1438 bufsize -= bufsize % (16* 1024); 1439 // Round to 16 KB boundaries 1440 if (bufsize < kMinBufferSize) { 1441 // at least kMinBufferSize 1442 bufsize = kMinBufferSize; 1443 } else if (bufsize > kMaxBufferSize) { 1444 // no more than kMaxBufferSize 1445 bufsize = kMaxBufferSize; 1446 } 1447 } 1448 1449 BFile destFile(destDir, destName, O_RDWR | O_CREAT); 1450 #ifdef _SILENTLY_CORRECT_FILE_NAMES 1451 if ((destFile.InitCheck() == B_BAD_VALUE 1452 || destFile.InitCheck() == B_NOT_ALLOWED) 1453 && CreateFileSystemCompatibleName(destDir, destName)) { 1454 destFile.SetTo(destDir, destName, B_CREATE_FILE | B_READ_WRITE); 1455 } 1456 #endif 1457 1458 ThrowOnInitCheckError(&destFile); 1459 1460 node_ref destNodeRef; 1461 destDir->GetNodeRef(&destNodeRef); 1462 // copy or write new pose location as a first thing 1463 SetupPoseLocation(ref.directory, destNodeRef.node, &srcFile, 1464 &destFile, loc); 1465 1466 char* buffer = new char[bufsize]; 1467 try { 1468 // copy data portion of file 1469 while (true) { 1470 if (loopControl->CheckUserCanceled()) { 1471 // if copy was canceled, remove partial destination file 1472 destFile.Unset(); 1473 1474 BEntry destEntry; 1475 if (destDir->FindEntry(destName, &destEntry) == B_OK) 1476 destEntry.Remove(); 1477 1478 throw (status_t)kCopyCanceled; 1479 } 1480 1481 ASSERT(buffer); 1482 ssize_t bytes = srcFile.Read(buffer, bufsize); 1483 1484 if (bytes > 0) { 1485 ssize_t updateBytes = 0; 1486 if (bytes > 32* 1024) { 1487 // when copying large chunks, update after read and after 1488 // write to get better update granularity 1489 updateBytes = bytes / 2; 1490 loopControl->UpdateStatus(NULL, ref, updateBytes, true); 1491 } 1492 1493 loopControl->ChecksumChunk(buffer, (size_t)bytes); 1494 1495 ssize_t result = destFile.Write(buffer, (size_t)bytes); 1496 if (result != bytes) { 1497 if (result < 0) 1498 throw (status_t)result; 1499 throw (status_t)B_ERROR; 1500 } 1501 1502 result = destFile.Sync(); 1503 if (result != B_OK) 1504 throw (status_t)result; 1505 1506 loopControl->UpdateStatus(NULL, ref, bytes - updateBytes, 1507 true); 1508 } else if (bytes < 0) { 1509 // read error 1510 throw (status_t)bytes; 1511 } else { 1512 // we are done 1513 break; 1514 } 1515 } 1516 1517 CopyAttributes(loopControl, &srcFile, &destFile, buffer, bufsize); 1518 } catch (...) { 1519 delete[] buffer; 1520 throw; 1521 } 1522 1523 destFile.SetPermissions(srcStat->st_mode); 1524 destFile.SetOwner(srcStat->st_uid); 1525 destFile.SetGroup(srcStat->st_gid); 1526 destFile.SetModificationTime(srcStat->st_mtime); 1527 destFile.SetCreationTime(srcStat->st_crtime); 1528 1529 delete[] buffer; 1530 1531 if (!loopControl->ChecksumFile(&ref)) { 1532 // File no good. Remove and quit. 1533 destFile.Unset(); 1534 1535 BEntry destEntry; 1536 if (destDir->FindEntry(destName, &destEntry) == B_OK) 1537 destEntry.Remove(); 1538 throw (status_t)kUserCanceled; 1539 } 1540 } 1541 1542 1543 void 1544 CopyAttributes(CopyLoopControl* control, BNode* srcNode, BNode* destNode, 1545 void* buffer, size_t bufsize) 1546 { 1547 // ToDo: 1548 // Add error checking 1549 // prior to coyping attributes, make sure indices are installed 1550 1551 // When calling CopyAttributes on files, have to make sure destNode 1552 // is a BFile opened R/W 1553 1554 srcNode->RewindAttrs(); 1555 char name[256]; 1556 while (srcNode->GetNextAttrName(name) == B_OK) { 1557 // Check to see if this attribute should be skipped. 1558 if (control->SkipAttribute(name)) 1559 continue; 1560 1561 attr_info info; 1562 if (srcNode->GetAttrInfo(name, &info) != B_OK) 1563 continue; 1564 1565 // Check to see if this attribute should be overwritten when it 1566 // already exists. 1567 if (control->PreserveAttribute(name)) { 1568 attr_info dest_info; 1569 if (destNode->GetAttrInfo(name, &dest_info) == B_OK) 1570 continue; 1571 } 1572 1573 // Special case for a size 0 attribute. It wouldn't be written at all 1574 // otherwise. 1575 if (info.size == 0) 1576 destNode->WriteAttr(name, info.type, 0, buffer, 0); 1577 1578 ssize_t bytes; 1579 ssize_t numToRead = (ssize_t)info.size; 1580 for (off_t offset = 0; numToRead > 0; offset += bytes) { 1581 size_t chunkSize = (size_t)numToRead; 1582 if (chunkSize > bufsize) 1583 chunkSize = bufsize; 1584 1585 bytes = srcNode->ReadAttr(name, info.type, offset, 1586 buffer, chunkSize); 1587 1588 if (bytes <= 0) 1589 break; 1590 1591 destNode->WriteAttr(name, info.type, offset, buffer, 1592 (size_t)bytes); 1593 1594 numToRead -= bytes; 1595 } 1596 } 1597 } 1598 1599 1600 static void 1601 CopyFolder(BEntry* srcEntry, BDirectory* destDir, 1602 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName, 1603 Undo &undo, bool removeSource = false) 1604 { 1605 BDirectory newDir; 1606 BEntry entry; 1607 status_t err = B_OK; 1608 bool createDirectory = true; 1609 BEntry existingEntry; 1610 1611 if (loopControl->SkipEntry(srcEntry, false)) 1612 return; 1613 1614 entry_ref ref; 1615 srcEntry->GetRef(&ref); 1616 1617 char destName[B_FILE_NAME_LENGTH]; 1618 strlcpy(destName, ref.name, sizeof(destName)); 1619 1620 loopControl->UpdateStatus(ref.name, ref, 1024, true); 1621 1622 if (makeOriginalName) { 1623 BString suffix(" "); 1624 suffix << B_TRANSLATE_COMMENT("copy", "filename copy"), 1625 FSMakeOriginalName(destName, destDir, suffix.String()); 1626 undo.UpdateEntry(srcEntry, destName); 1627 } 1628 1629 if (destDir->FindEntry(destName, &existingEntry) == B_OK) { 1630 // some entry with a conflicting name is already present in destDir 1631 // decide what to do about it 1632 bool isDirectory = existingEntry.IsDirectory(); 1633 1634 switch (loopControl->OverwriteOnConflict(srcEntry, destName, destDir, 1635 true, isDirectory)) { 1636 case TrackerCopyLoopControl::kSkip: 1637 // we are about to ignore this entire directory 1638 return; 1639 1640 1641 case TrackerCopyLoopControl::kReplace: 1642 if (!isDirectory) { 1643 // conflicting with a file or symbolic link, remove entry 1644 ThrowOnError(existingEntry.Remove()); 1645 break; 1646 } 1647 // fall through if directory, do not replace. 1648 case TrackerCopyLoopControl::kMerge: 1649 ASSERT(isDirectory); 1650 // do not create a new directory, use the current one 1651 newDir.SetTo(&existingEntry); 1652 createDirectory = false; 1653 break; 1654 } 1655 } 1656 1657 // loop through everything in src folder and copy it to new folder 1658 BDirectory srcDir(srcEntry); 1659 srcDir.Rewind(); 1660 1661 // create a new folder inside of destination folder 1662 if (createDirectory) { 1663 err = destDir->CreateDirectory(destName, &newDir); 1664 #ifdef _SILENTLY_CORRECT_FILE_NAMES 1665 if (err == B_BAD_VALUE) { 1666 // check if it's an invalid name on a FAT32 file system 1667 if (CreateFileSystemCompatibleName(destDir, destName)) 1668 err = destDir->CreateDirectory(destName, &newDir); 1669 } 1670 #endif 1671 if (err != B_OK) { 1672 if (!loopControl->FileError(B_TRANSLATE_NOCOLLECT( 1673 kFolderErrorString), destName, err, true)) { 1674 throw err; 1675 } 1676 1677 // will allow rest of copy to continue 1678 return; 1679 } 1680 } 1681 1682 char* buffer; 1683 if (createDirectory && err == B_OK 1684 && (buffer = (char*)malloc(32768)) != 0) { 1685 CopyAttributes(loopControl, &srcDir, &newDir, buffer, 32768); 1686 // don't copy original pose location if new location passed 1687 free(buffer); 1688 } 1689 1690 StatStruct statbuf; 1691 srcDir.GetStat(&statbuf); 1692 dev_t sourceDeviceID = statbuf.st_dev; 1693 1694 // copy or write new pose location 1695 node_ref destNodeRef; 1696 destDir->GetNodeRef(&destNodeRef); 1697 SetupPoseLocation(ref.directory, destNodeRef.node, &srcDir, 1698 &newDir, loc); 1699 1700 while (srcDir.GetNextEntry(&entry) == B_OK) { 1701 1702 if (loopControl->CheckUserCanceled()) 1703 throw (status_t)kUserCanceled; 1704 1705 entry.GetStat(&statbuf); 1706 1707 if (S_ISDIR(statbuf.st_mode)) { 1708 1709 // entry is a mount point, do not copy it 1710 if (statbuf.st_dev != sourceDeviceID) { 1711 PRINT(("Avoiding mount point %" B_PRIdDEV ", %" B_PRIdDEV "\n", 1712 statbuf.st_dev, sourceDeviceID)); 1713 continue; 1714 } 1715 1716 CopyFolder(&entry, &newDir, loopControl, 0, false, undo, 1717 removeSource); 1718 if (removeSource) 1719 FSDeleteFolder(&entry, loopControl, true, true, false); 1720 } else if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) { 1721 CopyFile(&entry, &statbuf, &newDir, loopControl, 0, false, undo); 1722 if (removeSource) 1723 entry.Remove(); 1724 } else { 1725 // Ignore special files 1726 } 1727 } 1728 if (removeSource) 1729 srcEntry->Remove(); 1730 else 1731 srcEntry->Unset(); 1732 } 1733 1734 1735 status_t 1736 RecursiveMove(BEntry* entry, BDirectory* destDir, 1737 CopyLoopControl* loopControl) 1738 { 1739 const char* name = entry->Name(); 1740 1741 if (destDir->Contains(name)) { 1742 BPath path (destDir, name); 1743 BDirectory subDir (path.Path()); 1744 entry_ref ref; 1745 entry->GetRef(&ref); 1746 BDirectory source(&ref); 1747 if (source.InitCheck() == B_OK) { 1748 source.Rewind(); 1749 BEntry current; 1750 while (source.GetNextEntry(¤t) == B_OK) { 1751 if (current.IsDirectory()) { 1752 RecursiveMove(¤t, &subDir, loopControl); 1753 current.Remove(); 1754 } else { 1755 name = current.Name(); 1756 if (loopControl->OverwriteOnConflict(¤t, name, 1757 &subDir, true, false) 1758 != TrackerCopyLoopControl::kSkip) { 1759 MoveError::FailOnError(current.MoveTo(&subDir, 1760 NULL, true)); 1761 } 1762 } 1763 } 1764 } 1765 entry->Remove(); 1766 } else 1767 MoveError::FailOnError(entry->MoveTo(destDir)); 1768 1769 return B_OK; 1770 } 1771 1772 status_t 1773 MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc, uint32 moveMode, 1774 const char* newName, Undo &undo, CopyLoopControl* loopControl) 1775 { 1776 entry_ref ref; 1777 try { 1778 node_ref destNode; 1779 StatStruct statbuf; 1780 MoveError::FailOnError(entry->GetStat(&statbuf)); 1781 MoveError::FailOnError(entry->GetRef(&ref)); 1782 MoveError::FailOnError(destDir->GetNodeRef(&destNode)); 1783 1784 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { 1785 PoseInfo poseInfo; 1786 char name[B_FILE_NAME_LENGTH]; 1787 strlcpy(name, ref.name, sizeof(name)); 1788 1789 BSymLink link; 1790 BString suffix(" "); 1791 suffix << B_TRANSLATE_COMMENT("link", "filename link"), 1792 FSMakeOriginalName(name, destDir, suffix.String()); 1793 undo.UpdateEntry(entry, name); 1794 1795 BPath path; 1796 entry->GetPath(&path); 1797 if (loc && loc != (BPoint*)-1) { 1798 poseInfo.fInvisible = false; 1799 poseInfo.fInitedDirectory = destNode.node; 1800 poseInfo.fLocation = *loc; 1801 } 1802 1803 status_t err = B_ERROR; 1804 1805 if (moveMode == kCreateRelativeLink) { 1806 if (statbuf.st_dev == destNode.device) { 1807 // relative link only works on the same device 1808 char oldwd[B_PATH_NAME_LENGTH]; 1809 getcwd(oldwd, B_PATH_NAME_LENGTH); 1810 1811 BEntry destEntry; 1812 destDir -> GetEntry(&destEntry); 1813 BPath destPath; 1814 destEntry.GetPath(&destPath); 1815 1816 chdir(destPath.Path()); 1817 // change working dir to target dir 1818 1819 BString destString(destPath.Path()); 1820 destString.Append("/"); 1821 1822 BString srcString(path.Path()); 1823 srcString.RemoveLast(path.Leaf()); 1824 1825 // find index while paths are the same 1826 1827 const char* src = srcString.String(); 1828 const char* dest = destString.String(); 1829 const char* lastFolderSrc = src; 1830 const char* lastFolderDest = dest; 1831 1832 while (*src && *dest && *src == *dest) { 1833 ++src; 1834 if (*dest++ == '/') { 1835 lastFolderSrc = src; 1836 lastFolderDest = dest; 1837 } 1838 } 1839 src = lastFolderSrc; 1840 dest = lastFolderDest; 1841 1842 BString source; 1843 if (*dest == '\0' && *src != '\0') { 1844 // source is deeper in the same tree than the target 1845 source.Append(src); 1846 } else if (*dest != '\0') { 1847 // target is deeper in the same tree than the source 1848 while (*dest) { 1849 if (*dest == '/') 1850 source.Prepend("../"); 1851 ++dest; 1852 } 1853 source.Append(src); 1854 } 1855 1856 // else source and target are in the same dir 1857 1858 source.Append(path.Leaf()); 1859 err = destDir->CreateSymLink(name, source.String(), 1860 &link); 1861 1862 chdir(oldwd); 1863 // change working dir back to original 1864 } else 1865 moveMode = kCreateLink; 1866 // fall back to absolute link mode 1867 } 1868 1869 if (moveMode == kCreateLink) 1870 err = destDir->CreateSymLink(name, path.Path(), &link); 1871 1872 if (err == B_UNSUPPORTED) { 1873 throw FailWithAlert(err, 1874 B_TRANSLATE("The target disk does not support " 1875 "creating links."), NULL); 1876 } 1877 1878 FailWithAlert::FailOnError(err, 1879 B_TRANSLATE("Error creating link to \"%name\"."), 1880 ref.name); 1881 1882 if (loc && loc != (BPoint*)-1) { 1883 link.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, 1884 sizeof(PoseInfo)); 1885 } 1886 1887 BNodeInfo nodeInfo(&link); 1888 nodeInfo.SetType(B_LINK_MIMETYPE); 1889 return B_OK; 1890 } 1891 1892 // if move is on same volume don't copy 1893 if (statbuf.st_dev == destNode.device && moveMode != kCopySelectionTo 1894 && moveMode != kDuplicateSelection) { 1895 1896 // for "Move" the size for status is always 1 - since file 1897 // size is irrelevant when simply moving to a new folder 1898 loopControl->UpdateStatus(ref.name, ref, 1); 1899 if (entry->IsDirectory()) 1900 return RecursiveMove(entry, destDir, loopControl); 1901 1902 MoveError::FailOnError(entry->MoveTo(destDir, newName)); 1903 } else { 1904 bool makeOriginalName = (moveMode == kDuplicateSelection); 1905 if (S_ISDIR(statbuf.st_mode)) { 1906 CopyFolder(entry, destDir, loopControl, loc, makeOriginalName, 1907 undo, moveMode == kMoveSelectionTo); 1908 } else { 1909 CopyFile(entry, &statbuf, destDir, loopControl, loc, 1910 makeOriginalName, undo); 1911 if (moveMode == kMoveSelectionTo) 1912 entry->Remove(); 1913 } 1914 } 1915 } catch (status_t error) { 1916 // no alert, was already taken care of before 1917 return error; 1918 } catch (MoveError& error) { 1919 BString errorString(B_TRANSLATE("Error moving \"%name\"")); 1920 errorString.ReplaceFirst("%name", ref.name); 1921 BAlert* alert = new BAlert("", errorString.String(), B_TRANSLATE("OK"), 1922 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1923 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1924 alert->Go(); 1925 return error.fError; 1926 } catch (FailWithAlert& error) { 1927 BString buffer(error.fString); 1928 if (error.fName != NULL) 1929 buffer.ReplaceFirst("%name", error.fName); 1930 else 1931 buffer << error.fString; 1932 1933 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("OK"), 1934 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1935 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1936 alert->Go(); 1937 1938 return error.fError; 1939 } 1940 1941 return B_OK; 1942 } 1943 1944 1945 void 1946 FSDuplicate(BObjectList<entry_ref>* srcList, BList* pointList) 1947 { 1948 LaunchInNewThread("DupTask", B_NORMAL_PRIORITY, MoveTask, srcList, 1949 (BEntry*)NULL, pointList, kDuplicateSelection); 1950 } 1951 1952 1953 #if 0 1954 status_t 1955 FSCopyFolder(BEntry* srcEntry, BDirectory* destDir, 1956 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName) 1957 { 1958 try 1959 CopyFolder(srcEntry, destDir, loopControl, loc, makeOriginalName); 1960 catch (status_t error) { 1961 return error; 1962 1963 return B_OK; 1964 } 1965 #endif 1966 1967 1968 status_t 1969 FSCopyAttributesAndStats(BNode* srcNode, BNode* destNode, bool copyTimes) 1970 { 1971 char* buffer = new char[1024]; 1972 1973 // copy the attributes 1974 srcNode->RewindAttrs(); 1975 char name[256]; 1976 while (srcNode->GetNextAttrName(name) == B_OK) { 1977 attr_info info; 1978 if (srcNode->GetAttrInfo(name, &info) != B_OK) 1979 continue; 1980 1981 attr_info dest_info; 1982 if (destNode->GetAttrInfo(name, &dest_info) == B_OK) 1983 continue; 1984 1985 ssize_t bytes; 1986 ssize_t numToRead = (ssize_t)info.size; 1987 for (off_t offset = 0; numToRead > 0; offset += bytes) { 1988 size_t chunkSize = (size_t)numToRead; 1989 if (chunkSize > 1024) 1990 chunkSize = 1024; 1991 1992 bytes = srcNode->ReadAttr(name, info.type, offset, buffer, 1993 chunkSize); 1994 1995 if (bytes <= 0) 1996 break; 1997 1998 destNode->WriteAttr(name, info.type, offset, buffer, 1999 (size_t)bytes); 2000 2001 numToRead -= bytes; 2002 } 2003 } 2004 delete[] buffer; 2005 2006 // copy the file stats 2007 struct stat srcStat; 2008 srcNode->GetStat(&srcStat); 2009 destNode->SetPermissions(srcStat.st_mode); 2010 destNode->SetOwner(srcStat.st_uid); 2011 destNode->SetGroup(srcStat.st_gid); 2012 if (copyTimes) { 2013 destNode->SetModificationTime(srcStat.st_mtime); 2014 destNode->SetCreationTime(srcStat.st_crtime); 2015 } 2016 2017 return B_OK; 2018 } 2019 2020 2021 #if 0 2022 status_t 2023 FSCopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir, 2024 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName) 2025 { 2026 try { 2027 CopyFile(srcFile, srcStat, destDir, loopControl, loc, 2028 makeOriginalName); 2029 } catch (status_t error) { 2030 return error; 2031 } 2032 2033 return B_OK; 2034 } 2035 #endif 2036 2037 2038 static status_t 2039 MoveEntryToTrash(BEntry* entry, BPoint* loc, Undo &undo) 2040 { 2041 BDirectory trash_dir; 2042 entry_ref ref; 2043 status_t result = entry->GetRef(&ref); 2044 if (result != B_OK) 2045 return result; 2046 2047 node_ref nodeRef; 2048 result = entry->GetNodeRef(&nodeRef); 2049 if (result != B_OK) 2050 return result; 2051 2052 StatStruct statbuf; 2053 result = entry->GetStat(&statbuf); 2054 if (entry->GetStat(&statbuf) != B_OK) 2055 return result; 2056 2057 // if it's a directory close the window and any child dir windows 2058 if (S_ISDIR(statbuf.st_mode)) { 2059 BDirectory dir(entry); 2060 2061 // if it's a volume, try to unmount 2062 if (dir.IsRootDirectory()) { 2063 BVolume volume(nodeRef.device); 2064 BVolume boot; 2065 2066 BVolumeRoster().GetBootVolume(&boot); 2067 if (volume == boot) { 2068 char name[B_FILE_NAME_LENGTH]; 2069 volume.GetName(name); 2070 BString buffer( 2071 B_TRANSLATE("Cannot unmount the boot volume \"%name\".")); 2072 buffer.ReplaceFirst("%name", name); 2073 BAlert* alert = new BAlert("", buffer.String(), 2074 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 2075 B_WARNING_ALERT); 2076 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2077 alert->Go(); 2078 } else { 2079 BMessage message(kUnmountVolume); 2080 message.AddInt32("device_id", volume.Device()); 2081 be_app->PostMessage(&message); 2082 } 2083 return B_OK; 2084 } 2085 2086 // get trash directory on same volume as item being moved 2087 result = FSGetTrashDir(&trash_dir, nodeRef.device); 2088 if (result != B_OK) 2089 return result; 2090 2091 // check hierarchy before moving 2092 BEntry trashEntry; 2093 trash_dir.GetEntry(&trashEntry); 2094 2095 if (dir == trash_dir || dir.Contains(&trashEntry)) { 2096 BAlert* alert = new BAlert("", 2097 B_TRANSLATE("You cannot put the selected item(s) " 2098 "into the trash."), 2099 B_TRANSLATE("OK"), 2100 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2101 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2102 alert->Go(); 2103 2104 // return no error so we don't get two dialogs 2105 return B_OK; 2106 } 2107 2108 BMessage message(kCloseWindowAndChildren); 2109 2110 node_ref parentNode; 2111 parentNode.device = statbuf.st_dev; 2112 parentNode.node = statbuf.st_ino; 2113 message.AddData("node_ref", B_RAW_TYPE, &parentNode, sizeof(node_ref)); 2114 be_app->PostMessage(&message); 2115 } else { 2116 // get trash directory on same volume as item being moved 2117 result = FSGetTrashDir(&trash_dir, nodeRef.device); 2118 if (result != B_OK) 2119 return result; 2120 } 2121 2122 // make sure name doesn't conflict with anything in trash already 2123 char name[B_FILE_NAME_LENGTH]; 2124 strlcpy(name, ref.name, sizeof(name)); 2125 if (trash_dir.Contains(name)) { 2126 BString suffix(" "); 2127 suffix << B_TRANSLATE_COMMENT("copy", "filename copy"), 2128 FSMakeOriginalName(name, &trash_dir, suffix.String()); 2129 undo.UpdateEntry(entry, name); 2130 } 2131 2132 BNode* src_node = 0; 2133 if (loc && loc != (BPoint*)-1 2134 && (src_node = GetWritableNode(entry, &statbuf)) != 0) { 2135 trash_dir.GetStat(&statbuf); 2136 PoseInfo poseInfo; 2137 poseInfo.fInvisible = false; 2138 poseInfo.fInitedDirectory = statbuf.st_ino; 2139 poseInfo.fLocation = *loc; 2140 src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, 2141 sizeof(poseInfo)); 2142 delete src_node; 2143 } 2144 2145 BNode node(entry); 2146 BPath path; 2147 // Get path of entry before it's moved to the trash 2148 // and write it to the file as an attribute 2149 if (node.InitCheck() == B_OK && entry->GetPath(&path) == B_OK) { 2150 BString originalPath(path.Path()); 2151 node.WriteAttrString(kAttrOriginalPath, &originalPath); 2152 } 2153 2154 TrackerCopyLoopControl loopControl; 2155 MoveItem(entry, &trash_dir, loc, kMoveSelectionTo, name, undo, 2156 &loopControl); 2157 return B_OK; 2158 } 2159 2160 2161 ConflictCheckResult 2162 PreFlightNameCheck(BObjectList<entry_ref>* srcList, const BDirectory* destDir, 2163 int32* collisionCount, uint32 moveMode) 2164 { 2165 // count the number of name collisions in dest folder 2166 *collisionCount = 0; 2167 2168 int32 count = srcList->CountItems(); 2169 for (int32 i = 0; i < count; i++) { 2170 entry_ref* srcRef = srcList->ItemAt(i); 2171 BEntry entry(srcRef); 2172 BDirectory parent; 2173 entry.GetParent(&parent); 2174 2175 if (parent != *destDir && destDir->Contains(srcRef->name)) 2176 (*collisionCount)++; 2177 } 2178 2179 // prompt user only if there is more than one collision, otherwise the 2180 // single collision case will be handled as a "Prompt" case by CheckName 2181 if (*collisionCount > 1) { 2182 const char* verb = (moveMode == kMoveSelectionTo) 2183 ? B_TRANSLATE("moving") : B_TRANSLATE("copying"); 2184 BString replaceMsg(B_TRANSLATE_NOCOLLECT(kReplaceManyStr)); 2185 replaceMsg.ReplaceAll("%verb", verb); 2186 2187 BAlert* alert = new BAlert(); 2188 alert->SetText(replaceMsg.String()); 2189 alert->AddButton(B_TRANSLATE("Cancel")); 2190 alert->AddButton(B_TRANSLATE("Prompt")); 2191 alert->AddButton(B_TRANSLATE("Skip all")); 2192 alert->AddButton(B_TRANSLATE("Replace all")); 2193 alert->SetShortcut(0, B_ESCAPE); 2194 switch (alert->Go()) { 2195 case 0: 2196 return kCanceled; 2197 2198 case 1: 2199 // user selected "Prompt" 2200 return kPrompt; 2201 2202 case 2: 2203 // user selected "Skip all" 2204 return kSkipAll; 2205 2206 case 3: 2207 // user selected "Replace all" 2208 return kReplaceAll; 2209 } 2210 } 2211 2212 return kNoConflicts; 2213 } 2214 2215 2216 void 2217 FileStatToString(StatStruct* stat, char* buffer, int32 length) 2218 { 2219 tm timeData; 2220 localtime_r(&stat->st_mtime, &timeData); 2221 2222 BString size; 2223 static BStringFormat format( 2224 B_TRANSLATE("{0, plural, one{# byte} other{# bytes}}")); 2225 format.Format(size, stat->st_size); 2226 uint32 pos = snprintf(buffer, length, "\n\t(%s ", size.String()); 2227 2228 strftime(buffer + pos, length - pos, "%b %d %Y, %I:%M:%S %p)", &timeData); 2229 } 2230 2231 2232 status_t 2233 CheckName(uint32 moveMode, const BEntry* sourceEntry, 2234 const BDirectory* destDir, bool multipleCollisions, 2235 ConflictCheckResult& conflictResolution) 2236 { 2237 if (moveMode == kDuplicateSelection) { 2238 // when duplicating, we will never have a conflict 2239 return B_OK; 2240 } 2241 2242 // see if item already exists in destination dir 2243 const char* name = sourceEntry->Name(); 2244 bool sourceIsDirectory = sourceEntry->IsDirectory(); 2245 2246 BDirectory srcDirectory; 2247 if (sourceIsDirectory) { 2248 srcDirectory.SetTo(sourceEntry); 2249 BEntry destEntry; 2250 destDir->GetEntry(&destEntry); 2251 2252 if (moveMode != kCreateLink && moveMode != kCreateRelativeLink 2253 && (srcDirectory == *destDir 2254 || srcDirectory.Contains(&destEntry))) { 2255 BAlert* alert = new BAlert("", 2256 B_TRANSLATE("You can't move a folder into itself " 2257 "or any of its own sub-folders."), B_TRANSLATE("OK"), 2258 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2259 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2260 alert->Go(); 2261 return B_ERROR; 2262 } 2263 } 2264 2265 if (FSIsTrashDir(sourceEntry) && moveMode != kCreateLink 2266 && moveMode != kCreateRelativeLink) { 2267 BAlert* alert = new BAlert("", 2268 B_TRANSLATE("You can't move or copy the trash."), 2269 B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, 2270 B_WARNING_ALERT); 2271 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2272 alert->Go(); 2273 return B_ERROR; 2274 } 2275 2276 BEntry entry; 2277 if (destDir->FindEntry(name, &entry) != B_OK) { 2278 // no conflict, return 2279 return B_OK; 2280 } 2281 2282 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { 2283 // if we are creating link in the same directory, the conflict will 2284 // be handled later by giving the link a unique name 2285 sourceEntry->GetParent(&srcDirectory); 2286 2287 if (srcDirectory == *destDir) 2288 return B_OK; 2289 } 2290 2291 bool destIsDir = entry.IsDirectory(); 2292 // be sure not to replace the parent directory of the item being moved 2293 if (destIsDir) { 2294 BDirectory targetDir(&entry); 2295 if (targetDir.Contains(sourceEntry)) { 2296 BAlert* alert = new BAlert("", 2297 B_TRANSLATE("You can't replace a folder " 2298 "with one of its sub-folders."), 2299 B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2300 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2301 alert->Go(); 2302 return B_ERROR; 2303 } 2304 } 2305 2306 // ensure that the user isn't trying to replace a file with folder 2307 // or vice-versa 2308 if (moveMode != kCreateLink 2309 && moveMode != kCreateRelativeLink 2310 && destIsDir != sourceIsDirectory) { 2311 BAlert* alert = new BAlert("", sourceIsDirectory 2312 ? B_TRANSLATE("You cannot replace a file with a folder or a " 2313 "symbolic link.") 2314 : B_TRANSLATE("You cannot replace a folder or a symbolic link " 2315 "with a file."), B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, 2316 B_WARNING_ALERT); 2317 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2318 alert->Go(); 2319 return B_ERROR; 2320 } 2321 2322 if (conflictResolution == kSkipAll) 2323 return B_ERROR; 2324 2325 if (conflictResolution != kReplaceAll) { 2326 // prompt user to determine whether to replace or not 2327 BString replaceMsg; 2328 2329 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { 2330 replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kSymLinkReplaceStr)); 2331 replaceMsg.ReplaceFirst("%name", name); 2332 } else if (sourceEntry->IsDirectory()) { 2333 replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kDirectoryReplaceStr)); 2334 replaceMsg.ReplaceFirst("%name", name); 2335 replaceMsg.ReplaceFirst("%verb", 2336 moveMode == kMoveSelectionTo 2337 ? B_TRANSLATE("moving") 2338 : B_TRANSLATE("copying")); 2339 } else { 2340 char sourceBuffer[96], destBuffer[96]; 2341 StatStruct statBuffer; 2342 2343 if (!sourceEntry->IsDirectory() 2344 && sourceEntry->GetStat(&statBuffer) == B_OK) { 2345 FileStatToString(&statBuffer, sourceBuffer, 96); 2346 } else 2347 sourceBuffer[0] = '\0'; 2348 2349 if (!entry.IsDirectory() && entry.GetStat(&statBuffer) == B_OK) 2350 FileStatToString(&statBuffer, destBuffer, 96); 2351 else 2352 destBuffer[0] = '\0'; 2353 2354 replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kReplaceStr)); 2355 replaceMsg.ReplaceAll("%name", name); 2356 replaceMsg.ReplaceFirst("%dest", destBuffer); 2357 replaceMsg.ReplaceFirst("%src", sourceBuffer); 2358 replaceMsg.ReplaceFirst("%movemode", moveMode == kMoveSelectionTo 2359 ? B_TRANSLATE("moving") : B_TRANSLATE("copying")); 2360 } 2361 2362 // special case single collision (don't need Replace All shortcut) 2363 BAlert* alert; 2364 if (multipleCollisions || sourceIsDirectory) { 2365 alert = new BAlert(); 2366 alert->SetText(replaceMsg.String()); 2367 alert->AddButton(B_TRANSLATE("Skip")); 2368 alert->AddButton(B_TRANSLATE("Skip all")); 2369 alert->AddButton(B_TRANSLATE("Replace")); 2370 alert->AddButton(B_TRANSLATE("Replace all")); 2371 switch (alert->Go()) { 2372 case 0: 2373 conflictResolution = kCanceled; 2374 return B_ERROR; 2375 case 1: 2376 conflictResolution = kSkipAll; 2377 return B_ERROR; 2378 case 2: 2379 conflictResolution = kReplace; 2380 break; 2381 case 3: 2382 conflictResolution = kReplaceAll; 2383 break; 2384 } 2385 } else { 2386 alert = new BAlert("", replaceMsg.String(), 2387 B_TRANSLATE("Cancel"), B_TRANSLATE("Replace")); 2388 alert->SetShortcut(0, B_ESCAPE); 2389 switch (alert->Go()) { 2390 case 0: 2391 conflictResolution = kCanceled; 2392 return B_ERROR; 2393 case 1: 2394 conflictResolution = kReplace; 2395 break; 2396 } 2397 } 2398 } 2399 2400 // delete destination item 2401 if (destIsDir) 2402 return B_OK; 2403 2404 status_t status = entry.Remove(); 2405 if (status != B_OK) { 2406 BString error(B_TRANSLATE("There was a problem trying to replace " 2407 "\"%name\". The item might be open or busy.")); 2408 error.ReplaceFirst("%name", name); 2409 BAlert* alert = new BAlert("", error.String(), 2410 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2411 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2412 alert->Go(); 2413 } 2414 2415 return status; 2416 } 2417 2418 2419 status_t 2420 FSDeleteFolder(BEntry* dirEntry, CopyLoopControl* loopControl, 2421 bool updateStatus, bool deleteTopDir, bool upateFileNameInStatus) 2422 { 2423 BDirectory dir(dirEntry); 2424 2425 // loop through everything in folder and delete it, skipping trouble files 2426 BEntry entry; 2427 while (dir.GetNextEntry(&entry) == B_OK) { 2428 entry_ref ref; 2429 entry.GetRef(&ref); 2430 2431 if (loopControl->CheckUserCanceled()) 2432 return kTrashCanceled; 2433 2434 status_t status; 2435 2436 if (entry.IsDirectory()) 2437 status = FSDeleteFolder(&entry, loopControl, updateStatus, true, 2438 upateFileNameInStatus); 2439 else { 2440 status = entry.Remove(); 2441 if (updateStatus) { 2442 loopControl->UpdateStatus(upateFileNameInStatus ? ref.name 2443 : "", ref, 1, true); 2444 } 2445 } 2446 2447 if (status == kTrashCanceled) 2448 return kTrashCanceled; 2449 2450 if (status != B_OK) { 2451 loopControl->FileError(B_TRANSLATE_NOCOLLECT( 2452 kFileDeleteErrorString), ref.name, status, false); 2453 } 2454 } 2455 2456 if (loopControl->CheckUserCanceled()) 2457 return kTrashCanceled; 2458 2459 entry_ref ref; 2460 dirEntry->GetRef(&ref); 2461 2462 if (updateStatus && deleteTopDir) 2463 loopControl->UpdateStatus(NULL, ref, 1); 2464 2465 if (deleteTopDir) 2466 return dirEntry->Remove(); 2467 2468 return B_OK; 2469 } 2470 2471 2472 void 2473 FSMakeOriginalName(BString &string, const BDirectory* destDir, 2474 const char* suffix) 2475 { 2476 if (!destDir->Contains(string.String())) 2477 return; 2478 2479 FSMakeOriginalName(string.LockBuffer(B_FILE_NAME_LENGTH), 2480 const_cast<BDirectory*>(destDir), suffix ? suffix : " copy"); 2481 string.UnlockBuffer(); 2482 } 2483 2484 2485 void 2486 FSMakeOriginalName(char* name, BDirectory* destDir, const char* suffix) 2487 { 2488 char root[B_FILE_NAME_LENGTH]; 2489 char copybase[B_FILE_NAME_LENGTH]; 2490 char tempName[B_FILE_NAME_LENGTH + 11]; 2491 int32 fnum; 2492 2493 // is this name already original? 2494 if (!destDir->Contains(name)) 2495 return; 2496 2497 // Determine if we're copying a 'copy'. This algorithm isn't perfect. 2498 // If you're copying a file whose REAL name ends with 'copy' then 2499 // this method will return "<filename> 1", not "<filename> copy" 2500 2501 // However, it will correctly handle file that contain 'copy' 2502 // elsewhere in their name. 2503 2504 bool copycopy = false; // are we copying a copy? 2505 int32 len = (int32)strlen(name); 2506 char* p = name + len - 1; // get pointer to end os name 2507 2508 // eat up optional numbers (if were copying "<filename> copy 34") 2509 while ((p > name) && isdigit(*p)) 2510 p--; 2511 2512 // eat up optional spaces 2513 while ((p > name) && isspace(*p)) 2514 p--; 2515 2516 // now look for the phrase " copy" 2517 if (p > name) { 2518 // p points to the last char of the word. For example, 'y' in 'copy' 2519 2520 if ((p - 4 > name) && (strncmp(p - 4, suffix, 5) == 0)) { 2521 // we found 'copy' in the right place. 2522 // so truncate after 'copy' 2523 *(p + 1) = '\0'; 2524 copycopy = true; 2525 2526 // save the 'root' name of the file, for possible later use. 2527 // that is copy everything but trailing " copy". Need to 2528 // NULL terminate after copy 2529 strncpy(root, name, (uint32)((p - name) - 4)); 2530 root[(p - name) - 4] = '\0'; 2531 } 2532 } 2533 2534 if (!copycopy) { 2535 // The name can't be longer than B_FILE_NAME_LENGTH. 2536 // The algoritm adds " copy XX" to the name. That's 8 characters. 2537 // B_FILE_NAME_LENGTH already accounts for NULL termination so we 2538 // don't need to save an extra char at the end. 2539 if (strlen(name) > B_FILE_NAME_LENGTH - 8) { 2540 // name is too long - truncate it! 2541 name[B_FILE_NAME_LENGTH - 8] = '\0'; 2542 } 2543 2544 strlcpy(root, name, sizeof(root)); 2545 // save root name 2546 strlcat(name, suffix, B_FILE_NAME_LENGTH); 2547 } 2548 2549 strlcpy(copybase, name, sizeof(copybase)); 2550 2551 // if name already exists then add a number 2552 fnum = 1; 2553 strlcpy(tempName, name, sizeof(tempName)); 2554 while (destDir->Contains(tempName)) { 2555 snprintf(tempName, sizeof(tempName), "%s %" B_PRId32, copybase, 2556 ++fnum); 2557 2558 if (strlen(tempName) > (B_FILE_NAME_LENGTH - 1)) { 2559 // The name has grown too long. Maybe we just went from 2560 // "<filename> copy 9" to "<filename> copy 10" and that extra 2561 // character was too much. The solution is to further 2562 // truncate the 'root' name and continue. 2563 // ??? should we reset fnum or not ??? 2564 root[strlen(root) - 1] = '\0'; 2565 snprintf(tempName, sizeof(tempName), "%s%s %" B_PRId32, root, 2566 suffix, fnum); 2567 } 2568 } 2569 2570 strlcpy(name, tempName, B_FILE_NAME_LENGTH); 2571 } 2572 2573 2574 status_t 2575 FSRecursiveCalcSize(BInfoWindow* window, CopyLoopControl* loopControl, 2576 BDirectory* dir, off_t* _runningSize, int32* _fileCount, int32* _dirCount) 2577 { 2578 dir->Rewind(); 2579 BEntry entry; 2580 while (dir->GetNextEntry(&entry) == B_OK) { 2581 // be sure window hasn't closed 2582 if (window && window->StopCalc()) 2583 return B_OK; 2584 2585 if (loopControl->CheckUserCanceled()) 2586 return kUserCanceled; 2587 2588 StatStruct statbuf; 2589 status_t status = entry.GetStat(&statbuf); 2590 if (status != B_OK) 2591 return status; 2592 2593 (*_runningSize) += statbuf.st_blocks * 512; 2594 2595 if (S_ISDIR(statbuf.st_mode)) { 2596 BDirectory subdir(&entry); 2597 (*_dirCount)++; 2598 status = FSRecursiveCalcSize(window, loopControl, &subdir, 2599 _runningSize, _fileCount, _dirCount); 2600 if (status != B_OK) 2601 return status; 2602 } else 2603 (*_fileCount)++; 2604 } 2605 return B_OK; 2606 } 2607 2608 2609 status_t 2610 CalcItemsAndSize(CopyLoopControl* loopControl, 2611 BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount, 2612 off_t* totalSize) 2613 { 2614 int32 fileCount = 0; 2615 int32 dirCount = 0; 2616 2617 // check block size for sanity 2618 if (blockSize < 0) { 2619 // This would point at an error to retrieve the block size from 2620 // the target volume. The code below cannot be used, it is only 2621 // meant to get the block size when item operations happen on 2622 // the source volume. 2623 blockSize = 2048; 2624 } else if (blockSize < 1024) { 2625 blockSize = 1024; 2626 if (entry_ref* ref = refList->ItemAt(0)) { 2627 // TODO: This assumes all entries in the list share the same 2628 // volume... 2629 BVolume volume(ref->device); 2630 if (volume.InitCheck() == B_OK) 2631 blockSize = volume.BlockSize(); 2632 } 2633 } 2634 // File systems like ReiserFS may advertize a large block size, but 2635 // stuff is still packed into blocks, so clamp maximum block size. 2636 if (blockSize > 8192) 2637 blockSize = 8192; 2638 2639 int32 num_items = refList->CountItems(); 2640 for (int32 i = 0; i < num_items; i++) { 2641 entry_ref* ref = refList->ItemAt(i); 2642 BEntry entry(ref); 2643 StatStruct statbuf; 2644 entry.GetStat(&statbuf); 2645 2646 if (loopControl->CheckUserCanceled()) 2647 return kUserCanceled; 2648 2649 if (S_ISDIR(statbuf.st_mode)) { 2650 BDirectory dir(&entry); 2651 dirCount++; 2652 (*totalSize) += blockSize; 2653 status_t result = FSRecursiveCalcSize(NULL, loopControl, &dir, 2654 totalSize, &fileCount, &dirCount); 2655 if (result != B_OK) 2656 return result; 2657 } else { 2658 fileCount++; 2659 (*totalSize) += statbuf.st_size + blockSize; 2660 } 2661 } 2662 2663 *totalCount += (fileCount + dirCount); 2664 return B_OK; 2665 } 2666 2667 2668 status_t 2669 FSGetTrashDir(BDirectory* trashDir, dev_t dev) 2670 { 2671 if (trashDir == NULL) 2672 return B_BAD_VALUE; 2673 2674 BVolume volume(dev); 2675 status_t result = volume.InitCheck(); 2676 if (result != B_OK) 2677 return result; 2678 2679 BPath path; 2680 result = find_directory(B_TRASH_DIRECTORY, &path, false, &volume); 2681 if (result != B_OK) 2682 return result; 2683 2684 result = trashDir->SetTo(path.Path()); 2685 if (result != B_OK) { 2686 // Trash directory does not exist yet, create it. 2687 result = create_directory(path.Path(), 0755); 2688 if (result != B_OK) 2689 return result; 2690 2691 result = trashDir->SetTo(path.Path()); 2692 if (result != B_OK) 2693 return result; 2694 2695 // make Trash directory invisible 2696 StatStruct sbuf; 2697 trashDir->GetStat(&sbuf); 2698 2699 PoseInfo poseInfo; 2700 poseInfo.fInvisible = true; 2701 poseInfo.fInitedDirectory = sbuf.st_ino; 2702 trashDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, 2703 sizeof(PoseInfo)); 2704 } 2705 2706 // set Trash icons (if they haven't already been set) 2707 attr_info attrInfo; 2708 size_t size; 2709 const void* data; 2710 if (trashDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2711 data = GetTrackerResources()->LoadResource('ICON', R_TrashIcon, &size); 2712 if (data != NULL) 2713 trashDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size); 2714 } 2715 2716 if (trashDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2717 data = GetTrackerResources()->LoadResource('MICN', R_TrashIcon, &size); 2718 if (data != NULL) 2719 trashDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size); 2720 } 2721 2722 if (trashDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2723 data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE, 2724 R_TrashIcon, &size); 2725 if (data != NULL) 2726 trashDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size); 2727 } 2728 2729 return B_OK; 2730 } 2731 2732 2733 status_t 2734 FSGetDeskDir(BDirectory* deskDir) 2735 { 2736 if (deskDir == NULL) 2737 return B_BAD_VALUE; 2738 2739 BPath path; 2740 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true); 2741 if (result != B_OK) 2742 return result; 2743 2744 result = deskDir->SetTo(path.Path()); 2745 if (result != B_OK) 2746 return result; 2747 2748 // set Desktop icons (if they haven't already been set) 2749 attr_info attrInfo; 2750 size_t size; 2751 const void* data; 2752 if (deskDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2753 data = GetTrackerResources()->LoadResource('ICON', R_DeskIcon, &size); 2754 if (data != NULL) 2755 deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size); 2756 } 2757 2758 if (deskDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2759 data = GetTrackerResources()->LoadResource('MICN', R_DeskIcon, &size); 2760 if (data != NULL) 2761 deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size); 2762 } 2763 2764 if (deskDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2765 data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE, 2766 R_DeskIcon, &size); 2767 if (data != NULL) 2768 deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size); 2769 } 2770 2771 return B_OK; 2772 } 2773 2774 2775 status_t 2776 FSGetBootDeskDir(BDirectory* deskDir) 2777 { 2778 BVolume bootVolume; 2779 BVolumeRoster().GetBootVolume(&bootVolume); 2780 BPath path; 2781 2782 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true, 2783 &bootVolume); 2784 if (result != B_OK) 2785 return result; 2786 2787 return deskDir->SetTo(path.Path()); 2788 } 2789 2790 2791 static bool 2792 FSIsDirFlavor(const BEntry* entry, directory_which directoryType) 2793 { 2794 StatStruct dir_stat; 2795 StatStruct entry_stat; 2796 BVolume volume; 2797 BPath path; 2798 2799 if (entry->GetStat(&entry_stat) != B_OK) 2800 return false; 2801 2802 if (volume.SetTo(entry_stat.st_dev) != B_OK) 2803 return false; 2804 2805 if (find_directory(directoryType, &path, false, &volume) != B_OK) 2806 return false; 2807 2808 stat(path.Path(), &dir_stat); 2809 2810 return dir_stat.st_ino == entry_stat.st_ino 2811 && dir_stat.st_dev == entry_stat.st_dev; 2812 } 2813 2814 2815 bool 2816 FSIsPrintersDir(const BEntry* entry) 2817 { 2818 return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY); 2819 } 2820 2821 2822 bool 2823 FSIsTrashDir(const BEntry* entry) 2824 { 2825 return FSIsDirFlavor(entry, B_TRASH_DIRECTORY); 2826 } 2827 2828 2829 bool 2830 FSIsDeskDir(const BEntry* entry) 2831 { 2832 BPath path; 2833 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true); 2834 if (result != B_OK) 2835 return false; 2836 2837 BEntry entryToCompare(path.Path()); 2838 return entryToCompare == *entry; 2839 } 2840 2841 2842 bool 2843 FSIsHomeDir(const BEntry* entry) 2844 { 2845 return FSIsDirFlavor(entry, B_USER_DIRECTORY); 2846 } 2847 2848 2849 bool 2850 FSIsRootDir(const BEntry* entry) 2851 { 2852 BPath path; 2853 if (entry->InitCheck() != B_OK || entry->GetPath(&path) != B_OK) 2854 return false; 2855 2856 return strcmp(path.Path(), "/") == 0; 2857 } 2858 2859 2860 bool 2861 DirectoryMatchesOrContains(const BEntry* entry, directory_which which) 2862 { 2863 BPath path; 2864 if (find_directory(which, &path, false, NULL) != B_OK) 2865 return false; 2866 2867 BEntry dirEntry(path.Path()); 2868 if (dirEntry.InitCheck() != B_OK) 2869 return false; 2870 2871 if (dirEntry == *entry) 2872 // root level match 2873 return true; 2874 2875 BDirectory dir(&dirEntry); 2876 return dir.Contains(entry); 2877 } 2878 2879 2880 bool 2881 DirectoryMatchesOrContains(const BEntry* entry, const char* additionalPath, 2882 directory_which which) 2883 { 2884 BPath path; 2885 if (find_directory(which, &path, false, NULL) != B_OK) 2886 return false; 2887 2888 path.Append(additionalPath); 2889 BEntry dirEntry(path.Path()); 2890 if (dirEntry.InitCheck() != B_OK) 2891 return false; 2892 2893 if (dirEntry == *entry) 2894 // root level match 2895 return true; 2896 2897 BDirectory dir(&dirEntry); 2898 return dir.Contains(entry); 2899 } 2900 2901 2902 bool 2903 DirectoryMatches(const BEntry* entry, directory_which which) 2904 { 2905 BPath path; 2906 if (find_directory(which, &path, false, NULL) != B_OK) 2907 return false; 2908 2909 BEntry dirEntry(path.Path()); 2910 if (dirEntry.InitCheck() != B_OK) 2911 return false; 2912 2913 return dirEntry == *entry; 2914 } 2915 2916 2917 bool 2918 DirectoryMatches(const BEntry* entry, const char* additionalPath, 2919 directory_which which) 2920 { 2921 BPath path; 2922 if (find_directory(which, &path, false, NULL) != B_OK) 2923 return false; 2924 2925 path.Append(additionalPath); 2926 BEntry dirEntry(path.Path()); 2927 if (dirEntry.InitCheck() != B_OK) 2928 return false; 2929 2930 return dirEntry == *entry; 2931 } 2932 2933 2934 extern status_t 2935 FSFindTrackerSettingsDir(BPath* path, bool autoCreate) 2936 { 2937 status_t result = find_directory(B_USER_SETTINGS_DIRECTORY, path, 2938 autoCreate); 2939 if (result != B_OK) 2940 return result; 2941 2942 path->Append("Tracker"); 2943 2944 return mkdir(path->Path(), 0777) ? B_OK : errno; 2945 } 2946 2947 2948 bool 2949 FSInTrashDir(const entry_ref* ref) 2950 { 2951 BEntry entry(ref); 2952 if (entry.InitCheck() != B_OK) 2953 return false; 2954 2955 BDirectory trashDir; 2956 if (FSGetTrashDir(&trashDir, ref->device) != B_OK) 2957 return false; 2958 2959 return trashDir.Contains(&entry); 2960 } 2961 2962 2963 void 2964 FSEmptyTrash() 2965 { 2966 if (find_thread("_tracker_empty_trash_") != B_OK) { 2967 resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_", 2968 B_NORMAL_PRIORITY, NULL)); 2969 } 2970 } 2971 2972 2973 status_t 2974 empty_trash(void*) 2975 { 2976 // empty trash on all mounted volumes 2977 status_t status = B_OK; 2978 2979 TrackerCopyLoopControl loopControl(kTrashState); 2980 2981 // calculate the sum total of all items on all volumes in trash 2982 BObjectList<entry_ref> srcList; 2983 int32 totalCount = 0; 2984 off_t totalSize = 0; 2985 2986 BVolumeRoster volumeRoster; 2987 BVolume volume; 2988 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 2989 if (volume.IsReadOnly() || !volume.IsPersistent()) 2990 continue; 2991 2992 BDirectory trashDirectory; 2993 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK) 2994 continue; 2995 2996 BEntry entry; 2997 trashDirectory.GetEntry(&entry); 2998 2999 entry_ref ref; 3000 entry.GetRef(&ref); 3001 srcList.AddItem(&ref); 3002 status = CalcItemsAndSize(&loopControl, &srcList, volume.BlockSize(), 3003 &totalCount, &totalSize); 3004 if (status != B_OK) 3005 break; 3006 3007 srcList.MakeEmpty(); 3008 3009 // don't count trash directory itself 3010 totalCount--; 3011 } 3012 3013 if (status == B_OK) { 3014 loopControl.Init(totalCount, totalCount); 3015 3016 volumeRoster.Rewind(); 3017 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 3018 if (volume.IsReadOnly() || !volume.IsPersistent()) 3019 continue; 3020 3021 BDirectory trashDirectory; 3022 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK) 3023 continue; 3024 3025 BEntry entry; 3026 trashDirectory.GetEntry(&entry); 3027 status = FSDeleteFolder(&entry, &loopControl, true, false); 3028 } 3029 } 3030 3031 if (status != B_OK && status != kTrashCanceled && status != kUserCanceled) { 3032 BAlert* alert = new BAlert("", B_TRANSLATE("Error emptying Trash"), 3033 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3034 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3035 alert->Go(); 3036 } 3037 3038 return B_OK; 3039 } 3040 3041 3042 status_t 3043 _DeleteTask(BObjectList<entry_ref>* list, bool confirm) 3044 { 3045 if (confirm) { 3046 BAlert* alert = new BAlert("", 3047 B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr), 3048 B_TRANSLATE("Cancel"), B_TRANSLATE("Move to Trash"), 3049 B_TRANSLATE("Delete"), 3050 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 3051 3052 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3053 alert->SetShortcut(0, B_ESCAPE); 3054 alert->SetShortcut(1, 'm'); 3055 alert->SetShortcut(2, 'd'); 3056 3057 switch (alert->Go()) { 3058 case 0: 3059 delete list; 3060 return B_CANCELED; 3061 3062 case 1: 3063 default: 3064 FSMoveToTrash(list, NULL, false); 3065 return B_OK; 3066 3067 case 2: 3068 break; 3069 } 3070 } 3071 3072 TrackerCopyLoopControl loopControl(kDeleteState); 3073 3074 // calculate the sum total of all items on all volumes in trash 3075 int32 totalItems = 0; 3076 int64 totalSize = 0; 3077 3078 status_t status = CalcItemsAndSize(&loopControl, list, 0, &totalItems, 3079 &totalSize); 3080 if (status == B_OK) { 3081 loopControl.Init(totalItems, totalItems); 3082 3083 int32 count = list->CountItems(); 3084 for (int32 index = 0; index < count; index++) { 3085 entry_ref ref(*list->ItemAt(index)); 3086 BEntry entry(&ref); 3087 loopControl.UpdateStatus(ref.name, ref, 1, true); 3088 if (entry.IsDirectory()) 3089 status = FSDeleteFolder(&entry, &loopControl, true, true, true); 3090 else 3091 status = entry.Remove(); 3092 } 3093 3094 if (status != kTrashCanceled && status != kUserCanceled 3095 && status != B_OK) { 3096 BAlert* alert = new BAlert("", B_TRANSLATE("Error deleting items"), 3097 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 3098 B_WARNING_ALERT); 3099 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3100 alert->Go(); 3101 } 3102 } 3103 3104 delete list; 3105 3106 return B_OK; 3107 } 3108 3109 status_t 3110 FSRecursiveCreateFolder(BPath path) 3111 { 3112 BEntry entry(path.Path()); 3113 if (entry.InitCheck() != B_OK) { 3114 BPath parentPath; 3115 status_t err = path.GetParent(&parentPath); 3116 if (err != B_OK) 3117 return err; 3118 3119 err = FSRecursiveCreateFolder(parentPath); 3120 if (err != B_OK) 3121 return err; 3122 } 3123 3124 entry.SetTo(path.Path()); 3125 if (entry.Exists()) 3126 return B_FILE_EXISTS; 3127 3128 BDirectory parent; 3129 entry.GetParent(&parent); 3130 parent.CreateDirectory(entry.Name(), NULL); 3131 3132 return B_OK; 3133 } 3134 3135 status_t 3136 _RestoreTask(BObjectList<entry_ref>* list) 3137 { 3138 TrackerCopyLoopControl loopControl(kRestoreFromTrashState); 3139 3140 // calculate the sum total of all items that will be restored 3141 int32 totalItems = 0; 3142 int64 totalSize = 0; 3143 3144 status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems, 3145 &totalSize); 3146 if (err == B_OK) { 3147 loopControl.Init(totalItems, totalItems); 3148 3149 int32 count = list->CountItems(); 3150 for (int32 index = 0; index < count; index++) { 3151 entry_ref ref(*list->ItemAt(index)); 3152 BEntry entry(&ref); 3153 BPath originalPath; 3154 3155 loopControl.UpdateStatus(ref.name, ref, 1, true); 3156 3157 if (FSGetOriginalPath(&entry, &originalPath) != B_OK) 3158 continue; 3159 3160 BEntry originalEntry(originalPath.Path()); 3161 BPath parentPath; 3162 err = originalPath.GetParent(&parentPath); 3163 if (err != B_OK) 3164 continue; 3165 BEntry parentEntry(parentPath.Path()); 3166 3167 if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) { 3168 if (FSRecursiveCreateFolder(parentPath) == B_OK) { 3169 originalEntry.SetTo(originalPath.Path()); 3170 if (entry.InitCheck() != B_OK) 3171 continue; 3172 } 3173 } 3174 3175 if (!originalEntry.Exists()) { 3176 BDirectory dir(parentPath.Path()); 3177 if (dir.InitCheck() == B_OK) { 3178 const char* leafName = originalEntry.Name(); 3179 if (entry.MoveTo(&dir, leafName) == B_OK) { 3180 BNode node(&entry); 3181 if (node.InitCheck() == B_OK) 3182 node.RemoveAttr(kAttrOriginalPath); 3183 } 3184 } 3185 } 3186 3187 err = loopControl.CheckUserCanceled(); 3188 if (err != B_OK) 3189 break; 3190 } 3191 } 3192 3193 delete list; 3194 3195 return err; 3196 } 3197 3198 void 3199 FSCreateTrashDirs() 3200 { 3201 BVolume volume; 3202 BVolumeRoster roster; 3203 3204 roster.Rewind(); 3205 while (roster.GetNextVolume(&volume) == B_OK) { 3206 if (volume.IsReadOnly() || !volume.IsPersistent()) 3207 continue; 3208 3209 BDirectory trashDir; 3210 FSGetTrashDir(&trashDir, volume.Device()); 3211 } 3212 } 3213 3214 3215 status_t 3216 FSCreateNewFolder(const entry_ref* ref) 3217 { 3218 node_ref node; 3219 node.device = ref->device; 3220 node.node = ref->directory; 3221 3222 BDirectory dir(&node); 3223 status_t result = dir.InitCheck(); 3224 if (result != B_OK) 3225 return result; 3226 3227 // ToDo: is that really necessary here? 3228 BString name(ref->name); 3229 FSMakeOriginalName(name, &dir, "-"); 3230 3231 BDirectory newDir; 3232 result = dir.CreateDirectory(name.String(), &newDir); 3233 if (result != B_OK) 3234 return result; 3235 3236 BNodeInfo nodeInfo(&newDir); 3237 nodeInfo.SetType(B_DIR_MIMETYPE); 3238 3239 return result; 3240 } 3241 3242 3243 status_t 3244 FSCreateNewFolderIn(const node_ref* dirNode, entry_ref* newRef, 3245 node_ref* newNode) 3246 { 3247 BDirectory dir(dirNode); 3248 status_t result = dir.InitCheck(); 3249 if (result == B_OK) { 3250 char name[B_FILE_NAME_LENGTH]; 3251 strlcpy(name, B_TRANSLATE("New folder"), sizeof(name)); 3252 3253 int fnum = 1; 3254 while (dir.Contains(name)) { 3255 // if base name already exists then add a number 3256 // TODO: move this logic to FSMakeOriginalName 3257 if (++fnum > 9) 3258 snprintf(name, sizeof(name), B_TRANSLATE("New folder%d"), fnum); 3259 else 3260 snprintf(name, sizeof(name), B_TRANSLATE("New folder %d"), fnum); 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