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