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