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 invisible 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 if (data != NULL) 2149 deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size); 2150 2151 data = GetTrackerResources()-> 2152 LoadResource('MICN', kResDeskIcon, &size); 2153 if (data != NULL) 2154 deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size); 2155 2156 #ifdef __HAIKU__ 2157 data = GetTrackerResources()-> 2158 LoadResource(B_VECTOR_ICON_TYPE, kResDeskIcon, &size); 2159 if (data != NULL) 2160 deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size); 2161 #endif 2162 2163 return B_OK; 2164 } 2165 2166 2167 status_t 2168 FSGetBootDeskDir(BDirectory *deskDir) 2169 { 2170 BVolume bootVol; 2171 BVolumeRoster().GetBootVolume(&bootVol); 2172 BPath path; 2173 2174 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &bootVol); 2175 if (result != B_OK) 2176 return result; 2177 2178 return deskDir->SetTo(path.Path()); 2179 } 2180 2181 2182 static bool 2183 FSIsDirFlavor(const BEntry *entry, directory_which directoryType) 2184 { 2185 StatStruct dir_stat; 2186 StatStruct entry_stat; 2187 BVolume volume; 2188 BPath path; 2189 2190 if (entry->GetStat(&entry_stat) != B_OK) 2191 return false; 2192 2193 if (volume.SetTo(entry_stat.st_dev) != B_OK) 2194 return false; 2195 2196 if (find_directory(directoryType, &path, false, &volume) != B_OK) 2197 return false; 2198 2199 stat(path.Path(), &dir_stat); 2200 2201 return dir_stat.st_ino == entry_stat.st_ino 2202 && dir_stat.st_dev == entry_stat.st_dev; 2203 } 2204 2205 2206 bool 2207 FSIsPrintersDir(const BEntry *entry) 2208 { 2209 return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY); 2210 } 2211 2212 2213 bool 2214 FSIsTrashDir(const BEntry *entry) 2215 { 2216 return FSIsDirFlavor(entry, B_TRASH_DIRECTORY); 2217 } 2218 2219 2220 bool 2221 FSIsDeskDir(const BEntry *entry, dev_t device) 2222 { 2223 BVolume volume(device); 2224 status_t result = volume.InitCheck(); 2225 if (result != B_OK) 2226 return false; 2227 2228 BPath path; 2229 result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume); 2230 if (result != B_OK) 2231 return false; 2232 2233 BEntry entryToCompare(path.Path()); 2234 return entryToCompare == *entry; 2235 } 2236 2237 2238 bool 2239 FSIsDeskDir(const BEntry *entry) 2240 { 2241 entry_ref ref; 2242 if (entry->GetRef(&ref) != B_OK) 2243 return false; 2244 2245 return FSIsDeskDir(entry, ref.device); 2246 } 2247 2248 2249 bool 2250 FSIsSystemDir(const BEntry *entry) 2251 { 2252 return FSIsDirFlavor(entry, B_BEOS_SYSTEM_DIRECTORY); 2253 } 2254 2255 2256 bool 2257 FSIsBeOSDir(const BEntry *entry) 2258 { 2259 return FSIsDirFlavor(entry, B_BEOS_DIRECTORY); 2260 } 2261 2262 2263 bool 2264 FSIsHomeDir(const BEntry *entry) 2265 { 2266 return FSIsDirFlavor(entry, B_USER_DIRECTORY); 2267 } 2268 2269 2270 bool 2271 DirectoryMatchesOrContains(const BEntry *entry, directory_which which) 2272 { 2273 BPath path; 2274 if (find_directory(which, &path, false, NULL) != B_OK) 2275 return false; 2276 2277 BEntry dirEntry(path.Path()); 2278 if (dirEntry.InitCheck() != B_OK) 2279 return false; 2280 2281 if (dirEntry == *entry) 2282 // root level match 2283 return true; 2284 2285 BDirectory dir(&dirEntry); 2286 return dir.Contains(entry); 2287 } 2288 2289 2290 bool 2291 DirectoryMatchesOrContains(const BEntry *entry, const char *additionalPath, 2292 directory_which which) 2293 { 2294 BPath path; 2295 if (find_directory(which, &path, false, NULL) != B_OK) 2296 return false; 2297 2298 path.Append(additionalPath); 2299 BEntry dirEntry(path.Path()); 2300 if (dirEntry.InitCheck() != B_OK) 2301 return false; 2302 2303 if (dirEntry == *entry) 2304 // root level match 2305 return true; 2306 2307 BDirectory dir(&dirEntry); 2308 return dir.Contains(entry); 2309 } 2310 2311 2312 bool 2313 DirectoryMatches(const BEntry *entry, directory_which which) 2314 { 2315 BPath path; 2316 if (find_directory(which, &path, false, NULL) != B_OK) 2317 return false; 2318 2319 BEntry dirEntry(path.Path()); 2320 if (dirEntry.InitCheck() != B_OK) 2321 return false; 2322 2323 return dirEntry == *entry; 2324 } 2325 2326 2327 bool 2328 DirectoryMatches(const BEntry *entry, const char *additionalPath, directory_which which) 2329 { 2330 BPath path; 2331 if (find_directory(which, &path, false, NULL) != B_OK) 2332 return false; 2333 2334 path.Append(additionalPath); 2335 BEntry dirEntry(path.Path()); 2336 if (dirEntry.InitCheck() != B_OK) 2337 return false; 2338 2339 return dirEntry == *entry; 2340 } 2341 2342 2343 extern status_t 2344 FSFindTrackerSettingsDir(BPath *path, bool autoCreate) 2345 { 2346 status_t result = find_directory (B_USER_SETTINGS_DIRECTORY, path, autoCreate); 2347 if (result != B_OK) 2348 return result; 2349 2350 path->Append("Tracker"); 2351 2352 return mkdir(path->Path(), 0777) ? B_OK : errno; 2353 } 2354 2355 2356 bool 2357 FSInTrashDir(const entry_ref *ref) 2358 { 2359 BEntry entry(ref); 2360 if (entry.InitCheck() != B_OK) 2361 return false; 2362 2363 BDirectory trashDir; 2364 if (FSGetTrashDir(&trashDir, ref->device) != B_OK) 2365 return false; 2366 2367 return trashDir.Contains(&entry); 2368 } 2369 2370 2371 void 2372 FSEmptyTrash() 2373 { 2374 if (find_thread("_tracker_empty_trash_") != B_OK) { 2375 resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_", 2376 B_NORMAL_PRIORITY, NULL)); 2377 } 2378 } 2379 2380 2381 status_t 2382 empty_trash(void *) 2383 { 2384 // empty trash on all mounted volumes 2385 status_t err = B_OK; 2386 2387 thread_id thread = find_thread(NULL); 2388 if (gStatusWindow) 2389 gStatusWindow->CreateStatusItem(thread, kTrashState); 2390 2391 // calculate the sum total of all items on all volumes in trash 2392 BObjectList<entry_ref> srcList; 2393 int32 totalCount = 0; 2394 off_t totalSize = 0; 2395 2396 BVolumeRoster volumeRoster; 2397 BVolume volume; 2398 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 2399 if (volume.IsReadOnly() || !volume.IsPersistent()) 2400 continue; 2401 2402 BDirectory trashDirectory; 2403 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK) 2404 continue; 2405 2406 BEntry entry; 2407 trashDirectory.GetEntry(&entry); 2408 2409 entry_ref ref; 2410 entry.GetRef(&ref); 2411 srcList.AddItem(&ref); 2412 err = CalcItemsAndSize(&srcList, &totalCount, &totalSize); 2413 if (err != B_OK) 2414 break; 2415 2416 srcList.MakeEmpty(); 2417 2418 // don't count trash directory itself 2419 totalCount--; 2420 } 2421 2422 if (err == B_OK) { 2423 if (gStatusWindow) 2424 gStatusWindow->InitStatusItem(thread, totalCount, totalCount); 2425 2426 volumeRoster.Rewind(); 2427 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 2428 TrackerCopyLoopControl loopControl(thread); 2429 2430 if (volume.IsReadOnly() || !volume.IsPersistent()) 2431 continue; 2432 2433 BDirectory trashDirectory; 2434 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK) 2435 continue; 2436 2437 BEntry entry; 2438 trashDirectory.GetEntry(&entry); 2439 err = FSDeleteFolder(&entry, &loopControl, true, false); 2440 } 2441 } 2442 2443 if (err != B_OK && err != kTrashCanceled && err != kUserCanceled) { 2444 (new BAlert("", "Error emptying Trash!", "OK", NULL, NULL, 2445 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 2446 } 2447 2448 if (gStatusWindow) 2449 gStatusWindow->RemoveStatusItem(find_thread(NULL)); 2450 2451 return B_OK; 2452 } 2453 2454 2455 status_t 2456 _DeleteTask(BObjectList<entry_ref> *list, bool confirm) 2457 { 2458 if (confirm) { 2459 bool dontMoveToTrash = TrackerSettings().DontMoveFilesToTrash(); 2460 2461 if (!dontMoveToTrash) { 2462 BAlert *alert = new BAlert("", kDeleteConfirmationStr, 2463 "Cancel", "Move to Trash", "Delete", B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 2464 B_WARNING_ALERT); 2465 2466 alert->SetShortcut(0, B_ESCAPE); 2467 alert->SetShortcut(1, 'm'); 2468 alert->SetShortcut(2, 'd'); 2469 2470 switch (alert->Go()) { 2471 case 0: 2472 delete list; 2473 return B_OK; 2474 case 1: 2475 FSMoveToTrash(list, NULL, false); 2476 return B_OK; 2477 } 2478 } else { 2479 BAlert *alert = new BAlert("", kDeleteConfirmationStr, 2480 "Cancel", "Delete", NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 2481 B_WARNING_ALERT); 2482 2483 alert->SetShortcut(0, B_ESCAPE); 2484 alert->SetShortcut(1, 'd'); 2485 2486 if (!alert->Go()) { 2487 delete list; 2488 return B_OK; 2489 } 2490 } 2491 } 2492 2493 thread_id thread = find_thread(NULL); 2494 if (gStatusWindow) 2495 gStatusWindow->CreateStatusItem(thread, kDeleteState); 2496 2497 // calculate the sum total of all items on all volumes in trash 2498 int32 totalItems = 0; 2499 int64 totalSize = 0; 2500 2501 status_t err = CalcItemsAndSize(list, &totalItems, &totalSize); 2502 if (err == B_OK) { 2503 if (gStatusWindow) 2504 gStatusWindow->InitStatusItem(thread, totalItems, totalItems); 2505 2506 int32 count = list->CountItems(); 2507 TrackerCopyLoopControl loopControl(thread); 2508 for (int32 index = 0; index < count; index++) { 2509 entry_ref ref(*list->ItemAt(index)); 2510 BEntry entry(&ref); 2511 loopControl.UpdateStatus(ref.name, ref, 1, true); 2512 if (entry.IsDirectory()) 2513 err = FSDeleteFolder(&entry, &loopControl, true, true, true); 2514 else 2515 err = entry.Remove(); 2516 } 2517 2518 if (err != kTrashCanceled && err != kUserCanceled && err != B_OK) 2519 (new BAlert("", "Error Deleting items", "OK", NULL, NULL, 2520 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 2521 } 2522 if (gStatusWindow) 2523 gStatusWindow->RemoveStatusItem(find_thread(NULL)); 2524 2525 delete list; 2526 2527 return B_OK; 2528 } 2529 2530 status_t 2531 FSRecursiveCreateFolder(BPath path) 2532 { 2533 BEntry entry(path.Path()); 2534 if (entry.InitCheck() != B_OK) { 2535 BPath parentPath; 2536 status_t err = path.GetParent(&parentPath); 2537 if (err != B_OK) 2538 return err; 2539 2540 err = FSRecursiveCreateFolder(parentPath); 2541 if (err != B_OK) 2542 return err; 2543 } 2544 2545 entry.SetTo(path.Path()); 2546 if (entry.Exists()) 2547 return B_FILE_EXISTS; 2548 else { 2549 char name[B_FILE_NAME_LENGTH]; 2550 BDirectory parent; 2551 2552 entry.GetParent(&parent); 2553 entry.GetName(name); 2554 parent.CreateDirectory(name, NULL); 2555 } 2556 2557 return B_OK; 2558 } 2559 2560 status_t 2561 _RestoreTask(BObjectList<entry_ref> *list) 2562 { 2563 thread_id thread = find_thread(NULL); 2564 if (gStatusWindow) 2565 gStatusWindow->CreateStatusItem(thread, kRestoreFromTrashState); 2566 2567 // calculate the sum total of all items that will be restored 2568 int32 totalItems = 0; 2569 int64 totalSize = 0; 2570 2571 status_t err = CalcItemsAndSize(list, &totalItems, &totalSize); 2572 if (err == B_OK) { 2573 if (gStatusWindow) 2574 gStatusWindow->InitStatusItem(thread, totalItems, totalItems); 2575 2576 int32 count = list->CountItems(); 2577 TrackerCopyLoopControl loopControl(thread); 2578 for (int32 index = 0; index < count; index++) { 2579 entry_ref ref(*list->ItemAt(index)); 2580 BEntry entry(&ref); 2581 BPath originalPath; 2582 2583 loopControl.UpdateStatus(ref.name, ref, 1, true); 2584 2585 if (FSGetOriginalPath(&entry, &originalPath) != B_OK) 2586 continue; 2587 2588 BEntry originalEntry(originalPath.Path()); 2589 BPath parentPath; 2590 err = originalPath.GetParent(&parentPath); 2591 if (err != B_OK) 2592 continue; 2593 BEntry parentEntry(parentPath.Path()); 2594 2595 if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) { 2596 if (FSRecursiveCreateFolder(parentPath) == B_OK) { 2597 originalEntry.SetTo(originalPath.Path()); 2598 if (entry.InitCheck() != B_OK) 2599 continue; 2600 } 2601 } 2602 2603 if (!originalEntry.Exists()) { 2604 BDirectory dir(parentPath.Path()); 2605 if (dir.InitCheck() == B_OK) { 2606 char leafName[B_FILE_NAME_LENGTH]; 2607 originalEntry.GetName(leafName); 2608 if (entry.MoveTo(&dir, leafName) == B_OK) { 2609 BNode node(&entry); 2610 if (node.InitCheck() == B_OK) 2611 node.RemoveAttr(kAttrOriginalPath); 2612 } 2613 } 2614 } 2615 2616 err = loopControl.CheckUserCanceled(); 2617 if (err != B_OK) 2618 break; 2619 } 2620 } 2621 if (gStatusWindow) 2622 gStatusWindow->RemoveStatusItem(find_thread(NULL)); 2623 2624 delete list; 2625 2626 return err; 2627 } 2628 2629 void 2630 FSCreateTrashDirs() 2631 { 2632 BVolume volume; 2633 BVolumeRoster roster; 2634 2635 roster.Rewind(); 2636 while (roster.GetNextVolume(&volume) == B_OK) { 2637 if (volume.IsReadOnly() || !volume.IsPersistent()) 2638 continue; 2639 2640 BPath path; 2641 find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume); 2642 find_directory(B_TRASH_DIRECTORY, &path, true, &volume); 2643 2644 BDirectory trashDir; 2645 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) { 2646 size_t size; 2647 const void* data = GetTrackerResources()-> 2648 LoadResource('ICON', kResTrashIcon, &size); 2649 if (data != NULL) { 2650 trashDir.WriteAttr(kAttrLargeIcon, 'ICON', 0, 2651 data, size); 2652 } 2653 data = GetTrackerResources()-> 2654 LoadResource('MICN', kResTrashIcon, &size); 2655 if (data != NULL) { 2656 trashDir.WriteAttr(kAttrMiniIcon, 'MICN', 0, 2657 data, size); 2658 } 2659 #ifdef __HAIKU 2660 data = GetTrackerResources()-> 2661 LoadResource(B_VECTOR_ICON_TYPE, kResTrashIcon, &size); 2662 if (data != NULL) { 2663 trashDir.WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, 2664 data, size); 2665 } 2666 #endif 2667 } 2668 } 2669 } 2670 2671 2672 status_t 2673 FSCreateNewFolder(const entry_ref *ref) 2674 { 2675 node_ref node; 2676 node.device = ref->device; 2677 node.node = ref->directory; 2678 2679 BDirectory dir(&node); 2680 status_t result = dir.InitCheck(); 2681 if (result != B_OK) 2682 return result; 2683 2684 // ToDo: is that really necessary here? 2685 BString name(ref->name); 2686 FSMakeOriginalName(name, &dir, "-"); 2687 2688 BDirectory newDir; 2689 result = dir.CreateDirectory(name.String(), &newDir); 2690 if (result != B_OK) 2691 return result; 2692 2693 BNodeInfo nodeInfo(&newDir); 2694 nodeInfo.SetType(B_DIR_MIMETYPE); 2695 2696 return result; 2697 } 2698 2699 2700 status_t 2701 FSCreateNewFolderIn(const node_ref *dirNode, entry_ref *newRef, 2702 node_ref *newNode) 2703 { 2704 BDirectory dir(dirNode); 2705 status_t result = dir.InitCheck(); 2706 if (result == B_OK) { 2707 char name[B_FILE_NAME_LENGTH]; 2708 strcpy(name, "New Folder"); 2709 2710 int32 fnum = 1; 2711 while (dir.Contains(name)) { 2712 // if base name already exists then add a number 2713 // ToDo: 2714 // move this logic ot FSMakeOriginalName 2715 if (++fnum > 9) 2716 sprintf(name, "New Folder%ld", fnum); 2717 else 2718 sprintf(name, "New Folder %ld", fnum); 2719 } 2720 2721 BDirectory newDir; 2722 result = dir.CreateDirectory(name, &newDir); 2723 if (result == B_OK) { 2724 BEntry entry; 2725 newDir.GetEntry(&entry); 2726 entry.GetRef(newRef); 2727 entry.GetNodeRef(newNode); 2728 2729 BNodeInfo nodeInfo(&newDir); 2730 nodeInfo.SetType(B_DIR_MIMETYPE); 2731 2732 // add undo item 2733 NewFolderUndo undo(*newRef); 2734 return B_OK; 2735 } 2736 } 2737 2738 (new BAlert("", "Sorry, could not create a new folder.", "Cancel", 0, 0, 2739 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 2740 return result; 2741 } 2742 2743 2744 ReadAttrResult 2745 ReadAttr(const BNode *node, const char *hostAttrName, const char *foreignAttrName, 2746 type_code type, off_t offset, void *buffer, size_t length, 2747 void (*swapFunc)(void *), bool isForeign) 2748 { 2749 if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer, length) == (ssize_t)length) 2750 return kReadAttrNativeOK; 2751 2752 // PRINT(("trying %s\n", foreignAttrName)); 2753 // try the other endianness 2754 if (node->ReadAttr(foreignAttrName, type, offset, buffer, length) != (ssize_t)length) 2755 return kReadAttrFailed; 2756 2757 // PRINT(("got %s\n", foreignAttrName)); 2758 if (!swapFunc) 2759 return kReadAttrForeignOK; 2760 2761 (swapFunc)(buffer); 2762 // run the endian swapper 2763 2764 return kReadAttrForeignOK; 2765 } 2766 2767 2768 ReadAttrResult 2769 GetAttrInfo(const BNode *node, const char *hostAttrName, const char *foreignAttrName, 2770 type_code *type, size_t *size) 2771 { 2772 attr_info info; 2773 2774 if (node->GetAttrInfo(hostAttrName, &info) == B_OK) { 2775 if (type) 2776 *type = info.type; 2777 if (size) 2778 *size = (size_t)info.size; 2779 2780 return kReadAttrNativeOK; 2781 } 2782 2783 if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) { 2784 if (type) 2785 *type = info.type; 2786 if (size) 2787 *size = (size_t)info.size; 2788 2789 return kReadAttrForeignOK; 2790 } 2791 return kReadAttrFailed; 2792 } 2793 2794 // launching code 2795 2796 static status_t 2797 TrackerOpenWith(const BMessage *refs) 2798 { 2799 BMessage clone(*refs); 2800 ASSERT(dynamic_cast<TTracker *>(be_app)); 2801 ASSERT(clone.what); 2802 clone.AddInt32("launchUsingSelector", 0); 2803 // runs the Open With window 2804 be_app->PostMessage(&clone); 2805 2806 return B_OK; 2807 } 2808 2809 static void 2810 AsynchLaunchBinder(void (*func)(const entry_ref *, const BMessage *, bool on), 2811 const entry_ref *entry, const BMessage *message, bool on) 2812 { 2813 Thread::Launch(NewFunctionObject(func, entry, message, on), 2814 B_NORMAL_PRIORITY, "LaunchTask"); 2815 } 2816 2817 static bool 2818 SniffIfGeneric(const entry_ref *ref) 2819 { 2820 BNode node(ref); 2821 char type[B_MIME_TYPE_LENGTH]; 2822 BNodeInfo info(&node); 2823 if (info.GetType(type) == B_OK && strcasecmp(type, B_FILE_MIME_TYPE) != 0) 2824 // already has a type and it's not octet stream 2825 return false; 2826 2827 BPath path(ref); 2828 if (path.Path()) { 2829 // force a mimeset 2830 node.RemoveAttr(kAttrMIMEType); 2831 update_mime_info(path.Path(), 0, 1, 1); 2832 } 2833 2834 return true; 2835 } 2836 2837 static void 2838 SniffIfGeneric(const BMessage *refs) 2839 { 2840 entry_ref ref; 2841 for (int32 index = 0; ; index++) { 2842 if (refs->FindRef("refs", index, &ref) != B_OK) 2843 break; 2844 SniffIfGeneric(&ref); 2845 } 2846 } 2847 2848 static void 2849 _TrackerLaunchAppWithDocuments(const entry_ref *appRef, const BMessage *refs, bool openWithOK) 2850 { 2851 team_id team; 2852 2853 status_t error = B_ERROR; 2854 BString alertString; 2855 2856 for (int32 mimesetIt = 0; ; mimesetIt++) { 2857 error = be_roster->Launch(appRef, refs, &team); 2858 if (error == B_ALREADY_RUNNING) 2859 // app already running, not really an error 2860 error = B_OK; 2861 2862 if (error == B_OK) 2863 break; 2864 2865 if (mimesetIt > 0) 2866 break; 2867 2868 // failed to open, try mimesetting the refs and launching again 2869 SniffIfGeneric(refs); 2870 } 2871 2872 if (error == B_OK) { 2873 // close possible parent window, if specified 2874 const node_ref *nodeToClose = 0; 2875 int32 numBytes; 2876 refs->FindData("nodeRefsToClose", B_RAW_TYPE, (const void **)&nodeToClose, &numBytes); 2877 if (nodeToClose) 2878 dynamic_cast<TTracker *>(be_app)->CloseParent(*nodeToClose); 2879 } else { 2880 alertString << "Could not open \"" << appRef->name << "\" (" << strerror(error) << "). "; 2881 if (refs && openWithOK) { 2882 alertString << kFindAlternativeStr; 2883 if ((new BAlert("", alertString.String(), "Cancel", "Find", 0, 2884 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1) 2885 error = TrackerOpenWith(refs); 2886 } else 2887 (new BAlert("", alertString.String(), "Cancel", 0, 0, 2888 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 2889 } 2890 } 2891 2892 extern "C" char** environ; 2893 extern "C" 2894 #if !B_BEOS_VERSION_DANO 2895 _IMPEXP_ROOT 2896 #endif 2897 status_t _kload_image_etc_(int argc, char **argv, char **envp, 2898 char *buf, int bufsize); 2899 2900 2901 static status_t 2902 LoaderErrorDetails(const entry_ref *app, BString &details) 2903 { 2904 BPath path; 2905 BEntry appEntry(app, true); 2906 status_t result = appEntry.GetPath(&path); 2907 2908 if (result != B_OK) 2909 return result; 2910 2911 char *argv[2] = { const_cast<char *>(path.Path()), 0}; 2912 2913 #ifdef __HAIKU__ 2914 // ToDo: do this correctly! 2915 result = load_image(1, (const char **)argv, (const char **)environ); 2916 details.SetTo("ToDo: this is missing from Haiku"); 2917 #else 2918 result = _kload_image_etc_(1, argv, environ, details.LockBuffer(1024), 1024); 2919 details.UnlockBuffer(); 2920 #endif 2921 return B_OK; 2922 } 2923 2924 2925 static void 2926 _TrackerLaunchDocuments(const entry_ref */*doNotUse*/, const BMessage *refs, 2927 bool openWithOK) 2928 { 2929 BMessage copyOfRefs(*refs); 2930 2931 entry_ref documentRef; 2932 if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) 2933 // nothing to launch, we are done 2934 return; 2935 2936 status_t error = B_ERROR; 2937 entry_ref app; 2938 BMessage *refsToPass = NULL; 2939 BString alertString; 2940 const char *alternative = 0; 2941 2942 for (int32 mimesetIt = 0; ; mimesetIt++) { 2943 alertString = ""; 2944 error = be_roster->FindApp(&documentRef, &app); 2945 2946 if (error != B_OK && mimesetIt == 0) { 2947 SniffIfGeneric(©OfRefs); 2948 continue; 2949 } 2950 2951 if (error != B_OK) { 2952 alertString << "Could not find an application to open \"" << documentRef.name 2953 << "\" (" << strerror(error) << "). "; 2954 if (openWithOK) 2955 alternative = kFindApplicationStr; 2956 2957 break; 2958 } else { 2959 BEntry appEntry(&app, true); 2960 for (int32 index = 0;;) { 2961 // remove the app itself from the refs received so we don't try 2962 // to open ourselves 2963 entry_ref ref; 2964 if (copyOfRefs.FindRef("refs", index, &ref) != B_OK) 2965 break; 2966 2967 // deal with symlinks properly 2968 BEntry documentEntry(&ref, true); 2969 if (appEntry == documentEntry) { 2970 PRINT(("stripping %s, app %s \n", ref.name, app.name)); 2971 copyOfRefs.RemoveData("refs", index); 2972 } else { 2973 PRINT(("leaving %s, app %s \n", ref.name, app.name)); 2974 index++; 2975 } 2976 } 2977 2978 refsToPass = CountRefs(©OfRefs) > 0 ? ©OfRefs: 0; 2979 team_id team; 2980 error = be_roster->Launch(&app, refsToPass, &team); 2981 if (error == B_ALREADY_RUNNING) 2982 // app already running, not really an error 2983 error = B_OK; 2984 if (error == B_OK || mimesetIt != 0) 2985 break; 2986 2987 SniffIfGeneric(©OfRefs); 2988 } 2989 } 2990 2991 if (error != B_OK && alertString.Length() == 0) { 2992 BString loaderErrorString; 2993 bool openedDocuments = true; 2994 2995 if (!refsToPass) { 2996 // we just double clicked the app itself, do not offer to 2997 // find a handling app 2998 openWithOK = false; 2999 openedDocuments = false; 3000 } 3001 3002 if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) { 3003 alertString << "Could not open \"" << app.name 3004 << "\". The file is mistakenly marked as executable. "; 3005 3006 if (!openWithOK) { 3007 // offer the possibility to change the permissions 3008 3009 alertString << "\nShould this be fixed?"; 3010 if ((new BAlert("", alertString.String(), "Cancel", "Proceed", 0, 3011 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1) { 3012 BEntry entry(&documentRef); 3013 mode_t permissions; 3014 3015 error = entry.GetPermissions(&permissions); 3016 if (error == B_OK) 3017 error = entry.SetPermissions(permissions & ~(S_IXUSR | S_IXGRP | S_IXOTH)); 3018 if (error == B_OK) { 3019 // we updated the permissions, so let's try again 3020 _TrackerLaunchDocuments(NULL, refs, false); 3021 return; 3022 } else { 3023 alertString = "Could not update permissions of file \""; 3024 alertString << app.name << "\". " << strerror(error); 3025 } 3026 } else 3027 return; 3028 } 3029 3030 alternative = kFindApplicationStr; 3031 } else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) { 3032 alertString << "Could not open \"" << documentRef.name 3033 << "\" because application \"" << app.name << "\" is in the trash. "; 3034 alternative = kFindAlternativeStr; 3035 } else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) { 3036 alertString << "Could not open \"" << documentRef.name << "\" " 3037 << "(" << strerror(error) << "). "; 3038 alternative = kFindAlternativeStr; 3039 } else if (error == B_MISSING_SYMBOL 3040 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { 3041 alertString << "Could not open \"" << documentRef.name << "\" "; 3042 if (openedDocuments) 3043 alertString << "with application \"" << app.name << "\" "; 3044 alertString << "(Missing symbol: " << loaderErrorString << "). \n"; 3045 alternative = kFindAlternativeStr; 3046 } else if (error == B_MISSING_LIBRARY 3047 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { 3048 alertString << "Could not open \"" << documentRef.name << "\" "; 3049 if (openedDocuments) 3050 alertString << "with application \"" << app.name << "\" "; 3051 alertString << "(Missing library: " << loaderErrorString << "). \n"; 3052 alternative = kFindAlternativeStr; 3053 } else { 3054 alertString << "Could not open \"" << documentRef.name 3055 << "\" with application \"" << app.name << "\" (" << strerror(error) << "). "; 3056 alternative = kFindAlternativeStr; 3057 } 3058 } 3059 3060 if (error != B_OK) { 3061 if (openWithOK) { 3062 ASSERT(alternative); 3063 alertString << alternative; 3064 if ((new BAlert("", alertString.String(), "Cancel", "Find", 0, 3065 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1) 3066 error = TrackerOpenWith(refs); 3067 } else 3068 (new BAlert("", alertString.String(), "Cancel", 0, 0, 3069 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 3070 } 3071 } 3072 3073 // the following three calls don't return any reasonable error codes, 3074 // should fix that, making them void 3075 3076 status_t 3077 TrackerLaunch(const entry_ref *appRef, const BMessage *refs, bool async, bool openWithOK) 3078 { 3079 if (!async) 3080 _TrackerLaunchAppWithDocuments(appRef, refs, openWithOK); 3081 else 3082 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs, openWithOK); 3083 3084 return B_OK; 3085 } 3086 3087 status_t 3088 TrackerLaunch(const entry_ref *appRef, bool async) 3089 { 3090 if (!async) 3091 _TrackerLaunchAppWithDocuments(appRef, 0, false); 3092 else 3093 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false); 3094 3095 return B_OK; 3096 } 3097 3098 status_t 3099 TrackerLaunch(const BMessage *refs, bool async, bool openWithOK) 3100 { 3101 if (!async) 3102 _TrackerLaunchDocuments(0, refs, openWithOK); 3103 else 3104 AsynchLaunchBinder(&_TrackerLaunchDocuments, 0, refs, openWithOK); 3105 3106 return B_OK; 3107 } 3108 3109 status_t 3110 LaunchBrokenLink(const char *signature, const BMessage *refs) 3111 { 3112 // This call is to support a hacky workaround for double-clicking 3113 // broken refs for cifs 3114 be_roster->Launch(signature, const_cast<BMessage *>(refs)); 3115 return B_OK; 3116 } 3117 3118 // external launch calls; need to be robust, work if Tracker is not running 3119 3120 #if !B_BEOS_VERSION_DANO 3121 _IMPEXP_TRACKER 3122 #endif 3123 status_t 3124 FSLaunchItem(const entry_ref *application, const BMessage *refsReceived, 3125 bool async, bool openWithOK) 3126 { 3127 return TrackerLaunch(application, refsReceived, async, openWithOK); 3128 } 3129 3130 3131 #if !B_BEOS_VERSION_DANO 3132 _IMPEXP_TRACKER 3133 #endif 3134 status_t 3135 FSOpenWith(BMessage *listOfRefs) 3136 { 3137 status_t result = B_ERROR; 3138 listOfRefs->what = B_REFS_RECEIVED; 3139 3140 if (dynamic_cast<TTracker *>(be_app)) 3141 result = TrackerOpenWith(listOfRefs); 3142 else 3143 ASSERT(!"not yet implemented"); 3144 3145 return result; 3146 } 3147 3148 // legacy calls, need for compatibility 3149 3150 void 3151 FSOpenWithDocuments(const entry_ref *executable, BMessage *documents) 3152 { 3153 TrackerLaunch(executable, documents, true); 3154 delete documents; 3155 } 3156 3157 status_t 3158 FSLaunchUsing(const entry_ref *ref, BMessage *listOfRefs) 3159 { 3160 BMessage temp(B_REFS_RECEIVED); 3161 if (!listOfRefs) { 3162 ASSERT(ref); 3163 temp.AddRef("refs", ref); 3164 listOfRefs = &temp; 3165 } 3166 FSOpenWith(listOfRefs); 3167 return B_OK; 3168 } 3169 3170 status_t 3171 FSLaunchItem(const entry_ref *ref, BMessage* message, int32, bool async) 3172 { 3173 if (message) 3174 message->what = B_REFS_RECEIVED; 3175 3176 status_t result = TrackerLaunch(ref, message, async, true); 3177 delete message; 3178 return result; 3179 } 3180 3181 3182 void 3183 FSLaunchItem(const entry_ref *ref, BMessage *message, int32 workspace) 3184 { 3185 FSLaunchItem(ref, message, workspace, true); 3186 } 3187 3188 // Get the original path of an entry in the trash 3189 status_t 3190 FSGetOriginalPath(BEntry *entry, BPath *result) 3191 { 3192 status_t err; 3193 entry_ref ref; 3194 err = entry->GetRef(&ref); 3195 if (err != B_OK) 3196 return err; 3197 3198 // Only call the routine for entries in the trash 3199 if (!FSInTrashDir(&ref)) 3200 return B_ERROR; 3201 3202 BNode node(entry); 3203 BString originalPath; 3204 if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) { 3205 // We're in luck, the entry has the original path in an attribute 3206 err = result->SetTo(originalPath.String()); 3207 return err; 3208 } 3209 3210 // Iterate the parent directories to find one with 3211 // the original path attribute 3212 BEntry parent(*entry); 3213 err = parent.InitCheck(); 3214 if (err != B_OK) 3215 return err; 3216 3217 // walk up the directory structure until we find a node 3218 // with original path attribute 3219 do { 3220 // move to the parent of this node 3221 err = parent.GetParent(&parent); 3222 if (err != B_OK) 3223 return err; 3224 3225 // return if we are at the root of the trash 3226 if (FSIsTrashDir(&parent)) 3227 return B_ENTRY_NOT_FOUND; 3228 3229 // get the parent as a node 3230 err = node.SetTo(&parent); 3231 if (err != B_OK) 3232 return err; 3233 } while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK); 3234 3235 // Found the attribute, figure out there this file 3236 // used to live, based on the successfully-read attribute 3237 err = result->SetTo(originalPath.String()); 3238 if (err != B_OK) 3239 return err; 3240 3241 BPath path, pathParent; 3242 err = parent.GetPath(&pathParent); 3243 if (err != B_OK) 3244 return err; 3245 err = entry->GetPath(&path); 3246 if (err != B_OK) 3247 return err; 3248 result->Append(path.Path() + strlen(pathParent.Path()) + 1); 3249 // compute the new path by appending the offset of 3250 // the item we are locating, to the original path 3251 // of the parent 3252 return B_OK; 3253 } 3254 3255 directory_which 3256 WellKnowEntryList::Match(const node_ref *node) 3257 { 3258 const WellKnownEntry *result = MatchEntry(node); 3259 if (result) 3260 return result->which; 3261 3262 return (directory_which)-1; 3263 } 3264 3265 const WellKnowEntryList::WellKnownEntry * 3266 WellKnowEntryList::MatchEntry(const node_ref *node) 3267 { 3268 if (!self) 3269 self = new WellKnowEntryList(); 3270 3271 return self->MatchEntryCommon(node); 3272 } 3273 3274 const WellKnowEntryList::WellKnownEntry * 3275 WellKnowEntryList::MatchEntryCommon(const node_ref *node) 3276 { 3277 uint32 count = entries.size(); 3278 for (uint32 index = 0; index < count; index++) 3279 if (*node == entries[index].node) 3280 return &entries[index]; 3281 3282 return NULL; 3283 } 3284 3285 3286 void 3287 WellKnowEntryList::Quit() 3288 { 3289 delete self; 3290 self = NULL; 3291 } 3292 3293 void 3294 WellKnowEntryList::AddOne(directory_which which, const char *name) 3295 { 3296 BPath path; 3297 if (find_directory(which, &path, true) != B_OK) 3298 return; 3299 3300 BEntry entry(path.Path()); 3301 node_ref node; 3302 if (entry.GetNodeRef(&node) != B_OK) 3303 return; 3304 3305 entries.push_back(WellKnownEntry(&node, which, name)); 3306 } 3307 3308 void 3309 WellKnowEntryList::AddOne(directory_which which, directory_which base, 3310 const char *extra, const char *name) 3311 { 3312 BPath path; 3313 if (find_directory(base, &path, true) != B_OK) 3314 return; 3315 3316 path.Append(extra); 3317 BEntry entry(path.Path()); 3318 node_ref node; 3319 if (entry.GetNodeRef(&node) != B_OK) 3320 return; 3321 3322 entries.push_back(WellKnownEntry(&node, which, name)); 3323 } 3324 3325 void 3326 WellKnowEntryList::AddOne(directory_which which, const char *path, const char *name) 3327 { 3328 BEntry entry(path); 3329 node_ref node; 3330 if (entry.GetNodeRef(&node) != B_OK) 3331 return; 3332 3333 entries.push_back(WellKnownEntry(&node, which, name)); 3334 } 3335 3336 3337 WellKnowEntryList::WellKnowEntryList() 3338 { 3339 AddOne(B_BEOS_DIRECTORY, "beos"); 3340 AddOne((directory_which)B_BOOT_DISK, "/boot", "boot"); 3341 AddOne(B_USER_DIRECTORY, "home"); 3342 AddOne(B_BEOS_SYSTEM_DIRECTORY, "system"); 3343 3344 AddOne(B_BEOS_FONTS_DIRECTORY, "fonts"); 3345 AddOne(B_COMMON_FONTS_DIRECTORY, "fonts"); 3346 AddOne(B_USER_FONTS_DIRECTORY, "fonts"); 3347 3348 AddOne(B_BEOS_APPS_DIRECTORY, "apps"); 3349 AddOne(B_APPS_DIRECTORY, "apps"); 3350 AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY, B_USER_DESKBAR_DIRECTORY, 3351 "Applications", "apps"); 3352 3353 AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences"); 3354 AddOne(B_PREFERENCES_DIRECTORY, "preferences"); 3355 AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY, B_USER_DESKBAR_DIRECTORY, 3356 "Preferences", "preferences"); 3357 3358 AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail", "mail"); 3359 3360 AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY, "queries", "queries"); 3361 3362 3363 3364 AddOne(B_COMMON_DEVELOP_DIRECTORY, "develop"); 3365 AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY, B_USER_DESKBAR_DIRECTORY, 3366 "Development", "develop"); 3367 3368 AddOne(B_USER_CONFIG_DIRECTORY, "config"); 3369 3370 AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY, "people", "people"); 3371 3372 AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY, "Downloads", 3373 "Downloads"); 3374 } 3375 3376 WellKnowEntryList *WellKnowEntryList::self = NULL; 3377 3378 } 3379