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