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