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 // Special case for a size 0 attribute. It wouldn't be written at all 1150 // otherwise. 1151 if (info.size == 0) 1152 destNode->WriteAttr(name, info.type, 0, buffer, 0); 1153 1154 ssize_t bytes; 1155 ssize_t numToRead = (ssize_t)info.size; 1156 for (off_t offset = 0; numToRead > 0; offset += bytes) { 1157 size_t chunkSize = (size_t)numToRead; 1158 if (chunkSize > bufsize) 1159 chunkSize = bufsize; 1160 1161 bytes = srcNode->ReadAttr(name, info.type, offset, 1162 buffer, chunkSize); 1163 1164 if (bytes <= 0) 1165 break; 1166 1167 destNode->WriteAttr(name, info.type, offset, buffer, (size_t)bytes); 1168 1169 numToRead -= bytes; 1170 } 1171 } 1172 } 1173 1174 1175 static void 1176 CopyFolder(BEntry *srcEntry, BDirectory *destDir, CopyLoopControl *loopControl, 1177 BPoint *loc, bool makeOriginalName, Undo &undo) 1178 { 1179 BDirectory newDir; 1180 BEntry entry; 1181 status_t err = B_OK; 1182 bool createDirectory = true; 1183 BEntry existingEntry; 1184 1185 if (loopControl->SkipEntry(srcEntry, false)) 1186 return; 1187 1188 entry_ref ref; 1189 srcEntry->GetRef(&ref); 1190 1191 char destName[B_FILE_NAME_LENGTH]; 1192 strcpy(destName, ref.name); 1193 1194 loopControl->UpdateStatus(ref.name, ref, 1024, true); 1195 1196 if (makeOriginalName) { 1197 FSMakeOriginalName(destName, destDir, " copy"); 1198 undo.UpdateEntry(srcEntry, destName); 1199 } 1200 1201 if (destDir->FindEntry(destName, &existingEntry) == B_OK) { 1202 // some entry with a conflicting name is already present in destDir 1203 // decide what to do about it 1204 bool isDirectory = existingEntry.IsDirectory(); 1205 1206 switch (loopControl->OverwriteOnConflict(srcEntry, destName, destDir, 1207 true, isDirectory)) { 1208 case TrackerCopyLoopControl::kSkip: 1209 // we are about to ignore this entire directory 1210 return; 1211 1212 case TrackerCopyLoopControl::kReplace: 1213 if (isDirectory) 1214 // remove existing folder recursively 1215 ThrowOnError(FSDeleteFolder(&existingEntry, loopControl, false)); 1216 1217 else 1218 // conflicting with a file or symbolic link, remove entry 1219 ThrowOnError(existingEntry.Remove()); 1220 break; 1221 1222 case TrackerCopyLoopControl::kMerge: 1223 ASSERT(isDirectory); 1224 // do not create a new directory, use the current one 1225 newDir.SetTo(&existingEntry); 1226 createDirectory = false; 1227 break; 1228 } 1229 } 1230 1231 // loop through everything in src folder and copy it to new folder 1232 BDirectory srcDir(srcEntry); 1233 srcDir.Rewind(); 1234 srcEntry->Unset(); 1235 1236 // create a new folder inside of destination folder 1237 if (createDirectory) { 1238 err = destDir->CreateDirectory(destName, &newDir); 1239 #ifdef _SILENTLY_CORRECT_FILE_NAMES 1240 if (err == B_BAD_VALUE) { 1241 // check if it's an invalid name on a FAT32 file system 1242 if (CreateFileSystemCompatibleName(destDir, destName)) 1243 err = destDir->CreateDirectory(destName, &newDir); 1244 } 1245 #endif 1246 if (err != B_OK) { 1247 if (!loopControl->FileError(kFolderErrorString, destName, err, true)) 1248 throw err; 1249 1250 // will allow rest of copy to continue 1251 return; 1252 } 1253 } 1254 1255 char *buffer; 1256 if (createDirectory && err == B_OK && (buffer = (char*)malloc(32768)) != 0) { 1257 CopyAttributes(loopControl, &srcDir, &newDir, buffer, 32768); 1258 // don't copy original pose location if new location passed 1259 free(buffer); 1260 } 1261 1262 StatStruct statbuf; 1263 srcDir.GetStat(&statbuf); 1264 dev_t sourceDeviceID = statbuf.st_dev; 1265 1266 // copy or write new pose location 1267 node_ref destNodeRef; 1268 destDir->GetNodeRef(&destNodeRef); 1269 SetUpPoseLocation(ref.directory, destNodeRef.node, &srcDir, 1270 &newDir, loc); 1271 1272 while (srcDir.GetNextEntry(&entry) == B_OK) { 1273 1274 if (loopControl->CheckUserCanceled()) 1275 throw (status_t)kUserCanceled; 1276 1277 entry.GetStat(&statbuf); 1278 1279 if (S_ISDIR(statbuf.st_mode)) { 1280 1281 // entry is a mount point, do not copy it 1282 if (statbuf.st_dev != sourceDeviceID) { 1283 PRINT(("Avoiding mount point %d, %d \n", statbuf.st_dev, sourceDeviceID)); 1284 continue; 1285 } 1286 1287 CopyFolder(&entry, &newDir, loopControl, 0, false, undo); 1288 } else 1289 CopyFile(&entry, &statbuf, &newDir, loopControl, 0, false, undo); 1290 } 1291 } 1292 1293 1294 status_t 1295 MoveItem(BEntry *entry, BDirectory *destDir, BPoint *loc, uint32 moveMode, 1296 const char *newName, Undo &undo) 1297 { 1298 entry_ref ref; 1299 try { 1300 node_ref destNode; 1301 StatStruct statbuf; 1302 1303 MoveError::FailOnError(entry->GetStat(&statbuf)); 1304 MoveError::FailOnError(entry->GetRef(&ref)); 1305 MoveError::FailOnError(destDir->GetNodeRef(&destNode)); 1306 1307 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { 1308 PoseInfo poseInfo; 1309 char name[B_FILE_NAME_LENGTH]; 1310 strcpy(name, ref.name); 1311 1312 BSymLink link; 1313 FSMakeOriginalName(name, destDir, " link"); 1314 undo.UpdateEntry(entry, name); 1315 1316 BPath path; 1317 entry->GetPath(&path); 1318 if (loc && loc != (BPoint *)-1) { 1319 poseInfo.fInvisible = false; 1320 poseInfo.fInitedDirectory = destNode.node; 1321 poseInfo.fLocation = *loc; 1322 } 1323 1324 status_t err = B_ERROR; 1325 1326 if (moveMode == kCreateRelativeLink) { 1327 if (statbuf.st_dev == destNode.device) { 1328 // relative link only works on the same device 1329 char oldwd[B_PATH_NAME_LENGTH]; 1330 getcwd(oldwd, B_PATH_NAME_LENGTH); 1331 1332 BEntry destEntry; 1333 destDir -> GetEntry(&destEntry); 1334 BPath destPath; 1335 destEntry.GetPath(&destPath); 1336 1337 chdir(destPath.Path()); 1338 // change working dir to target dir 1339 1340 BString destString(destPath.Path()); 1341 destString.Append("/"); 1342 1343 BString srcString(path.Path()); 1344 srcString.RemoveLast(path.Leaf()); 1345 1346 // find index while paths are the same 1347 1348 const char *src = srcString.String(); 1349 const char *dest = destString.String(); 1350 const char *lastFolderSrc = src; 1351 const char *lastFolderDest = dest; 1352 1353 while (*src && *dest && *src == *dest) { 1354 ++src; 1355 if (*dest++ == '/') { 1356 lastFolderSrc = src; 1357 lastFolderDest = dest; 1358 } 1359 } 1360 src = lastFolderSrc; 1361 dest = lastFolderDest; 1362 1363 BString source; 1364 if (*dest == '\0' && *src != '\0') { 1365 // source is deeper in the same tree than the target 1366 source.Append(src); 1367 } else if (*dest != '\0') { 1368 // target is deeper in the same tree than the source 1369 while (*dest) { 1370 if (*dest == '/') 1371 source.Prepend("../"); 1372 ++dest; 1373 } 1374 source.Append(src); 1375 } 1376 1377 // else source and target are in the same dir 1378 1379 source.Append(path.Leaf()); 1380 err = destDir->CreateSymLink(name, source.String(), &link); 1381 1382 chdir(oldwd); 1383 // change working dir back to original 1384 } else 1385 moveMode = kCreateLink; 1386 // fall back to absolute link mode 1387 } 1388 1389 if (moveMode == kCreateLink) 1390 err = destDir->CreateSymLink(name, path.Path(), &link); 1391 1392 if (err == B_UNSUPPORTED) 1393 throw FailWithAlert(err, "The target disk does not support creating links.", NULL); 1394 1395 FailWithAlert::FailOnError(err, "Error creating link to \"%s\".", ref.name); 1396 1397 if (loc && loc != (BPoint *)-1) 1398 link.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, sizeof(PoseInfo)); 1399 1400 BNodeInfo nodeInfo(&link); 1401 nodeInfo.SetType(B_LINK_MIMETYPE); 1402 return B_OK; 1403 } 1404 1405 // if move is on same volume don't copy 1406 if (statbuf.st_dev == destNode.device && moveMode != kCopySelectionTo 1407 && moveMode != kDuplicateSelection) { 1408 1409 // for "Move" the size for status is always 1 - since file 1410 // size is irrelevant when simply moving to a new folder 1411 1412 thread_id thread = find_thread(NULL); 1413 if (gStatusWindow && gStatusWindow->HasStatus(thread)) 1414 gStatusWindow->UpdateStatus(thread, ref.name, 1); 1415 1416 MoveError::FailOnError(entry->MoveTo(destDir, newName)); 1417 } else { 1418 TrackerCopyLoopControl loopControl(find_thread(NULL)); 1419 1420 bool makeOriginalName = (moveMode == kDuplicateSelection); 1421 if (S_ISDIR(statbuf.st_mode)) 1422 CopyFolder(entry, destDir, &loopControl, loc, makeOriginalName, undo); 1423 else 1424 CopyFile(entry, &statbuf, destDir, &loopControl, loc, makeOriginalName, undo); 1425 } 1426 } catch (status_t error) { 1427 // no alert, was already taken care of before 1428 return error; 1429 } catch (MoveError error) { 1430 BString errorString; 1431 errorString << "Error moving \"" << ref.name << '"'; 1432 (new BAlert("", errorString.String(), "OK", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 1433 return error.fError; 1434 } catch (FailWithAlert error) { 1435 char buffer[256]; 1436 if (error.fName) 1437 sprintf(buffer, error.fString, error.fName); 1438 else 1439 strcpy(buffer, error.fString); 1440 (new BAlert("", buffer, "OK", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 1441 1442 return error.fError; 1443 } 1444 1445 return B_OK; 1446 } 1447 1448 1449 void 1450 FSDuplicate(BObjectList<entry_ref> *srcList, BList *pointList) 1451 { 1452 LaunchInNewThread("DupTask", B_NORMAL_PRIORITY, MoveTask, srcList, (BEntry *)NULL, 1453 pointList, kDuplicateSelection); 1454 } 1455 1456 1457 #if 0 1458 status_t 1459 FSCopyFolder(BEntry *srcEntry, BDirectory *destDir, CopyLoopControl *loopControl, 1460 BPoint *loc, bool makeOriginalName) 1461 { 1462 try { 1463 CopyFolder(srcEntry, destDir, loopControl, loc, makeOriginalName); 1464 } catch (status_t error) { 1465 return error; 1466 } 1467 1468 return B_OK; 1469 } 1470 #endif 1471 1472 1473 status_t 1474 FSCopyAttributesAndStats(BNode *srcNode, BNode *destNode) 1475 { 1476 char *buffer = new char[1024]; 1477 1478 // copy the attributes 1479 srcNode->RewindAttrs(); 1480 char name[256]; 1481 while (srcNode->GetNextAttrName(name) == B_OK) { 1482 attr_info info; 1483 if (srcNode->GetAttrInfo(name, &info) != B_OK) 1484 continue; 1485 1486 attr_info dest_info; 1487 if (destNode->GetAttrInfo(name, &dest_info) == B_OK) 1488 continue; 1489 1490 ssize_t bytes; 1491 ssize_t numToRead = (ssize_t)info.size; 1492 for (off_t offset = 0; numToRead > 0; offset += bytes) { 1493 size_t chunkSize = (size_t)numToRead; 1494 if (chunkSize > 1024) 1495 chunkSize = 1024; 1496 1497 bytes = srcNode->ReadAttr(name, info.type, offset, buffer, chunkSize); 1498 1499 if (bytes <= 0) 1500 break; 1501 1502 destNode->WriteAttr(name, info.type, offset, buffer, (size_t)bytes); 1503 1504 numToRead -= bytes; 1505 } 1506 } 1507 delete[] buffer; 1508 1509 // copy the file stats 1510 struct stat srcStat; 1511 srcNode->GetStat(&srcStat); 1512 destNode->SetPermissions(srcStat.st_mode); 1513 destNode->SetOwner(srcStat.st_uid); 1514 destNode->SetGroup(srcStat.st_gid); 1515 destNode->SetModificationTime(srcStat.st_mtime); 1516 destNode->SetCreationTime(srcStat.st_crtime); 1517 1518 return B_OK; 1519 } 1520 1521 1522 #if 0 1523 status_t 1524 FSCopyFile(BEntry* srcFile, StatStruct *srcStat, BDirectory* destDir, 1525 CopyLoopControl *loopControl, BPoint *loc, bool makeOriginalName) 1526 { 1527 try { 1528 CopyFile(srcFile, srcStat, destDir, loopControl, loc, makeOriginalName); 1529 } catch (status_t error) { 1530 return error; 1531 } 1532 1533 return B_OK; 1534 } 1535 #endif 1536 1537 1538 static status_t 1539 MoveEntryToTrash(BEntry *entry, BPoint *loc, Undo &undo) 1540 { 1541 BDirectory trash_dir; 1542 entry_ref ref; 1543 status_t result = entry->GetRef(&ref); 1544 if (result != B_OK) 1545 return result; 1546 1547 node_ref nodeRef; 1548 result = entry->GetNodeRef(&nodeRef); 1549 if (result != B_OK) 1550 return result; 1551 1552 StatStruct statbuf; 1553 result = entry->GetStat(&statbuf); 1554 if (entry->GetStat(&statbuf) != B_OK) 1555 return result; 1556 1557 // if it's a directory close the window and any child dir windows 1558 if (S_ISDIR(statbuf.st_mode)) { 1559 BDirectory dir(entry); 1560 1561 // if it's a volume, try to unmount 1562 if (dir.IsRootDirectory()) { 1563 BVolume volume(nodeRef.device); 1564 BVolume boot; 1565 1566 BVolumeRoster().GetBootVolume(&boot); 1567 if (volume == boot) { 1568 char name[B_FILE_NAME_LENGTH]; 1569 volume.GetName(name); 1570 char buffer[256]; 1571 sprintf(buffer, "Cannot unmount the boot volume \"%s\".", name); 1572 (new BAlert("", buffer, "Cancel", 0, 0, 1573 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 1574 } else { 1575 BMessage message(kUnmountVolume); 1576 message.AddInt32("device_id", volume.Device()); 1577 be_app->PostMessage(&message); 1578 } 1579 return B_OK; 1580 } 1581 1582 // get trash directory on same volume as item being moved 1583 result = FSGetTrashDir(&trash_dir, nodeRef.device); 1584 if (result != B_OK) 1585 return result; 1586 1587 // check hierarchy before moving 1588 BEntry trashEntry; 1589 trash_dir.GetEntry(&trashEntry); 1590 1591 if (dir == trash_dir || dir.Contains(&trashEntry)) { 1592 (new BAlert("", "You cannot put the Trash, home or Desktop " 1593 "directory into the trash.", "OK", 0, 0, 1594 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 1595 1596 // return no error so we don't get two dialogs 1597 return B_OK; 1598 } 1599 1600 BMessage message(kCloseWindowAndChildren); 1601 1602 node_ref parentNode; 1603 parentNode.device = statbuf.st_dev; 1604 parentNode.node = statbuf.st_ino; 1605 message.AddData("node_ref", B_RAW_TYPE, &parentNode, sizeof(node_ref)); 1606 be_app->PostMessage(&message); 1607 } else { 1608 // get trash directory on same volume as item being moved 1609 result = FSGetTrashDir(&trash_dir, nodeRef.device); 1610 if (result != B_OK) 1611 return result; 1612 } 1613 1614 // make sure name doesn't conflict with anything in trash already 1615 char name[B_FILE_NAME_LENGTH]; 1616 strcpy(name, ref.name); 1617 if (trash_dir.Contains(name)) { 1618 FSMakeOriginalName(name, &trash_dir, " copy"); 1619 undo.UpdateEntry(entry, name); 1620 } 1621 1622 BNode *src_node = 0; 1623 if (loc && loc != (BPoint *)-1 1624 && (src_node = GetWritableNode(entry, &statbuf)) != 0) { 1625 trash_dir.GetStat(&statbuf); 1626 PoseInfo poseInfo; 1627 poseInfo.fInvisible = false; 1628 poseInfo.fInitedDirectory = statbuf.st_ino; 1629 poseInfo.fLocation = *loc; 1630 src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, 1631 sizeof(poseInfo)); 1632 delete src_node; 1633 } 1634 1635 BNode node(entry); 1636 BPath path; 1637 // Get path of entry before it's moved to the trash 1638 // and write it to the file as an attribute 1639 if (node.InitCheck() == B_OK && entry->GetPath(&path) == B_OK) { 1640 BString originalPath(path.Path()); 1641 node.WriteAttrString(kAttrOriginalPath, &originalPath); 1642 } 1643 1644 MoveItem(entry, &trash_dir, loc, kMoveSelectionTo, name, undo); 1645 return B_OK; 1646 } 1647 1648 1649 ConflictCheckResult 1650 PreFlightNameCheck(BObjectList<entry_ref> *srcList, const BDirectory *destDir, 1651 int32 *collisionCount) 1652 { 1653 1654 // count the number of name collisions in dest folder 1655 *collisionCount = 0; 1656 1657 int32 count = srcList->CountItems(); 1658 for (int32 i = 0; i < count; i++) { 1659 entry_ref *srcRef = srcList->ItemAt(i); 1660 BEntry entry(srcRef); 1661 BDirectory parent; 1662 entry.GetParent(&parent); 1663 1664 if (parent != *destDir) { 1665 if (destDir->Contains(srcRef->name)) 1666 (*collisionCount)++; 1667 } 1668 } 1669 1670 // prompt user only if there is more than one collision, otherwise the 1671 // single collision case will be handled as a "Prompt" case by CheckName 1672 if (*collisionCount > 1) { 1673 entry_ref *srcRef = (entry_ref*)srcList->FirstItem(); 1674 1675 StatStruct statbuf; 1676 destDir->GetStat(&statbuf); 1677 1678 const char *verb = (srcRef->device == statbuf.st_dev) ? "moving" : "copying"; 1679 char replaceMsg[256]; 1680 sprintf(replaceMsg, kReplaceManyStr, verb, verb); 1681 1682 switch ((new BAlert("", replaceMsg, "Cancel", "Prompt", "Replace All"))->Go()) { 1683 case 0: 1684 return kCanceled; 1685 1686 case 1: 1687 // user selected "Prompt" 1688 return kPrompt; 1689 1690 case 2: 1691 // user selected "Replace All" 1692 return kReplaceAll; 1693 } 1694 } 1695 1696 return kNoConflicts; 1697 } 1698 1699 1700 void 1701 FileStatToString(StatStruct *stat, char *buffer, int32 length) 1702 { 1703 tm timeData; 1704 localtime_r(&stat->st_mtime, &timeData); 1705 1706 sprintf(buffer, "\n\t(%Ld bytes, ", stat->st_size); 1707 uint32 pos = strlen(buffer); 1708 strftime(buffer + pos, length - pos,"%b %d %Y, %I:%M:%S %p)", &timeData); 1709 } 1710 1711 1712 status_t 1713 CheckName(uint32 moveMode, const BEntry *sourceEntry, const BDirectory *destDir, 1714 bool multipleCollisions, ConflictCheckResult &replaceAll) 1715 { 1716 if (moveMode == kDuplicateSelection) 1717 // when duplicating, we will never have a conflict 1718 return B_OK; 1719 1720 // see if item already exists in destination dir 1721 status_t err = B_OK; 1722 char name[B_FILE_NAME_LENGTH]; 1723 sourceEntry->GetName(name); 1724 bool sourceIsDirectory = sourceEntry->IsDirectory(); 1725 1726 BDirectory srcDirectory; 1727 if (sourceIsDirectory) { 1728 srcDirectory.SetTo(sourceEntry); 1729 BEntry destEntry; 1730 destDir->GetEntry(&destEntry); 1731 1732 if (moveMode != kCreateLink 1733 && moveMode != kCreateRelativeLink 1734 && (srcDirectory == *destDir || srcDirectory.Contains(&destEntry))) { 1735 (new BAlert("", "You can't move a folder into itself " 1736 "or any of its own sub-folders.", "OK", 0, 0, 1737 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 1738 return B_ERROR; 1739 } 1740 } 1741 1742 if (FSIsTrashDir(sourceEntry)) { 1743 (new BAlert("", "You can't move or copy the trash.", 1744 "OK", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 1745 return B_ERROR; 1746 } 1747 1748 BEntry entry; 1749 if (destDir->FindEntry(name, &entry) != B_OK) 1750 // no conflict, return 1751 return B_OK; 1752 1753 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { 1754 // if we are creating link in the same directory, the conflict will 1755 // be handled later by giving the link a unique name 1756 sourceEntry->GetParent(&srcDirectory); 1757 1758 if (srcDirectory == *destDir) 1759 return B_OK; 1760 } 1761 1762 bool destIsDir = entry.IsDirectory(); 1763 // be sure not to replace the parent directory of the item being moved 1764 if (destIsDir) { 1765 BDirectory test_dir(&entry); 1766 if (test_dir.Contains(sourceEntry)) { 1767 (new BAlert("", "You can't replace a folder " 1768 "with one of its sub-folders.", "OK", 0, 0, 1769 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 1770 return B_ERROR; 1771 } 1772 } 1773 1774 if (moveMode != kCreateLink 1775 && moveMode != kCreateRelativeLink 1776 && destIsDir != sourceIsDirectory) { 1777 // ensure user isn't trying to replace a file with folder or vice versa 1778 (new BAlert("", sourceIsDirectory 1779 ? "You cannot replace a file with a folder or a symbolic link." 1780 : "You cannot replace a folder or a symbolic link with a file.", 1781 "OK", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 1782 return B_ERROR; 1783 } 1784 1785 if (replaceAll != kReplaceAll) { 1786 // prompt user to determine whether to replace or not 1787 1788 char replaceMsg[512]; 1789 1790 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) 1791 sprintf(replaceMsg, kSymLinkReplaceStr, name); 1792 else if (sourceEntry->IsDirectory()) 1793 sprintf(replaceMsg, kDirectoryReplaceStr, name, 1794 moveMode == kMoveSelectionTo ? "moving" : "copying"); 1795 else { 1796 char sourceBuffer[96], destBuffer[96]; 1797 StatStruct statBuffer; 1798 1799 if (!sourceEntry->IsDirectory() && sourceEntry->GetStat(&statBuffer) == B_OK) 1800 FileStatToString(&statBuffer, sourceBuffer, 96); 1801 else 1802 sourceBuffer[0] = '\0'; 1803 1804 if (!entry.IsDirectory() && entry.GetStat(&statBuffer) == B_OK) 1805 FileStatToString(&statBuffer, destBuffer, 96); 1806 else 1807 destBuffer[0] = '\0'; 1808 1809 sprintf(replaceMsg, kReplaceStr, name, destBuffer, name, sourceBuffer, 1810 moveMode == kMoveSelectionTo ? "moving" : "copying"); 1811 } 1812 1813 // special case single collision (don't need Replace All shortcut) 1814 BAlert *alert; 1815 if (multipleCollisions) 1816 alert = new BAlert("", replaceMsg, "Skip", "Replace All", 1817 "Replace"); 1818 else 1819 alert = new BAlert("", replaceMsg, "Cancel", "Replace"); 1820 1821 switch (alert->Go()) { 1822 case 0: // user selected "Cancel" or "Skip" 1823 replaceAll = kCanceled; 1824 return B_ERROR; 1825 1826 case 1: // user selected "Replace" or "Replace All" 1827 replaceAll = kReplaceAll; 1828 // doesn't matter which since a single 1829 // collision "Replace" is equivalent to a 1830 // "Replace All" 1831 break; 1832 } 1833 } 1834 1835 // delete destination item 1836 if (destIsDir) { 1837 TrackerCopyLoopControl loopControl(find_thread(NULL)); 1838 err = FSDeleteFolder(&entry, &loopControl, false); 1839 } else 1840 err = entry.Remove(); 1841 1842 if (err != B_OK) { 1843 BString error; 1844 error << "There was a problem trying to replace \"" 1845 << name << "\". The item might be open or busy."; 1846 (new BAlert("", error.String(), "Cancel", 0, 0, 1847 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 1848 } 1849 1850 return err; 1851 } 1852 1853 1854 status_t 1855 FSDeleteFolder(BEntry *dir_entry, CopyLoopControl *loopControl, bool update_status, 1856 bool delete_top_dir, bool upateFileNameInStatus) 1857 { 1858 entry_ref ref; 1859 BEntry entry; 1860 BDirectory dir; 1861 status_t err; 1862 1863 dir.SetTo(dir_entry); 1864 dir.Rewind(); 1865 1866 // loop through everything in folder and delete it, skipping trouble files 1867 for (;;) { 1868 if (dir.GetNextEntry(&entry) != B_OK) 1869 break; 1870 1871 entry.GetRef(&ref); 1872 1873 if (loopControl->CheckUserCanceled()) 1874 return kTrashCanceled; 1875 1876 if (entry.IsDirectory()) 1877 err = FSDeleteFolder(&entry, loopControl, update_status, true, 1878 upateFileNameInStatus); 1879 else { 1880 err = entry.Remove(); 1881 if (update_status) 1882 loopControl->UpdateStatus(upateFileNameInStatus ? ref.name : "", ref, 1, true); 1883 } 1884 1885 if (err == kTrashCanceled) 1886 return kTrashCanceled; 1887 else if (err == B_OK) 1888 dir.Rewind(); 1889 else 1890 loopControl->FileError(kFileDeleteErrorString, ref.name, err, false); 1891 } 1892 1893 if (loopControl->CheckUserCanceled()) 1894 return kTrashCanceled; 1895 1896 dir_entry->GetRef(&ref); 1897 1898 if (update_status && delete_top_dir) 1899 loopControl->UpdateStatus(NULL, ref, 1); 1900 1901 if (delete_top_dir) 1902 return dir_entry->Remove(); 1903 else 1904 return B_OK; 1905 } 1906 1907 1908 void 1909 FSMakeOriginalName(BString &string, const BDirectory *destDir, const char *suffix) 1910 { 1911 if (!destDir->Contains(string.String())) 1912 return; 1913 1914 FSMakeOriginalName(string.LockBuffer(B_FILE_NAME_LENGTH), 1915 const_cast<BDirectory *>(destDir), suffix ? suffix : " copy"); 1916 string.UnlockBuffer(); 1917 } 1918 1919 1920 void 1921 FSMakeOriginalName(char *name, BDirectory *destDir, const char *suffix) 1922 { 1923 char root[B_FILE_NAME_LENGTH]; 1924 char copybase[B_FILE_NAME_LENGTH]; 1925 char temp_name[B_FILE_NAME_LENGTH + 10]; 1926 int32 fnum; 1927 1928 // is this name already original? 1929 if (!destDir->Contains(name)) 1930 return; 1931 1932 // Determine if we're copying a 'copy'. This algorithm isn't perfect. 1933 // If you're copying a file whose REAL name ends with 'copy' then 1934 // this method will return "<filename> 1", not "<filename> copy" 1935 1936 // However, it will correctly handle file that contain 'copy' 1937 // elsewhere in their name. 1938 1939 bool copycopy = false; // are we copying a copy? 1940 int32 len = (int32)strlen(name); 1941 char *p = name + len - 1; // get pointer to end os name 1942 1943 // eat up optional numbers (if were copying "<filename> copy 34") 1944 while ((p > name) && isdigit(*p)) 1945 p--; 1946 1947 // eat up optional spaces 1948 while ((p > name) && isspace(*p)) 1949 p--; 1950 1951 // now look for the phrase " copy" 1952 if (p > name) { 1953 // p points to the last char of the word. For example, 'y' in 'copy' 1954 1955 if ((p - 4 > name) && (strncmp(p - 4, suffix, 5) == 0)) { 1956 // we found 'copy' in the right place. 1957 // so truncate after 'copy' 1958 *(p + 1) = '\0'; 1959 copycopy = true; 1960 1961 // save the 'root' name of the file, for possible later use. 1962 // that is copy everything but trailing " copy". Need to 1963 // NULL terminate after copy 1964 strncpy(root, name, (uint32)((p - name) - 4)); 1965 root[(p - name) - 4] = '\0'; 1966 } 1967 } 1968 1969 if (!copycopy) { 1970 /* 1971 The name can't be longer than B_FILE_NAME_LENGTH. 1972 The algoritm adds " copy XX" to the name. That's 8 characters. 1973 B_FILE_NAME_LENGTH already accounts for NULL termination so we 1974 don't need to save an extra char at the end. 1975 */ 1976 if (strlen(name) > B_FILE_NAME_LENGTH - 8) { 1977 // name is too long - truncate it! 1978 name[B_FILE_NAME_LENGTH - 8] = '\0'; 1979 } 1980 1981 strcpy(root, name); // save root name 1982 strcat(name, suffix); 1983 } 1984 1985 strcpy(copybase, name); 1986 1987 // if name already exists then add a number 1988 fnum = 1; 1989 strcpy(temp_name, name); 1990 while (destDir->Contains(temp_name)) { 1991 sprintf(temp_name, "%s %ld", copybase, ++fnum); 1992 1993 if (strlen(temp_name) > (B_FILE_NAME_LENGTH - 1)) { 1994 /* 1995 The name has grown too long. Maybe we just went from 1996 "<filename> copy 9" to "<filename> copy 10" and that extra 1997 character was too much. The solution is to further 1998 truncate the 'root' name and continue. 1999 ??? should we reset fnum or not ??? 2000 */ 2001 root[strlen(root) - 1] = '\0'; 2002 sprintf(temp_name, "%s%s %ld", root, suffix, fnum); 2003 } 2004 } 2005 2006 ASSERT((strlen(temp_name) <= (B_FILE_NAME_LENGTH - 1))); 2007 strcpy(name, temp_name); 2008 } 2009 2010 2011 status_t 2012 FSRecursiveCalcSize(BInfoWindow *wind, BDirectory *dir, off_t *running_size, 2013 int32 *fileCount, int32 *dirCount) 2014 { 2015 thread_id tid = find_thread(NULL); 2016 2017 dir->Rewind(); 2018 BEntry entry; 2019 while (dir->GetNextEntry(&entry) == B_OK) { 2020 2021 // be sure window hasn't closed 2022 if (wind && wind->StopCalc()) 2023 return B_OK; 2024 2025 if (gStatusWindow && gStatusWindow->CheckCanceledOrPaused(tid)) 2026 return kUserCanceled; 2027 2028 StatStruct statbuf; 2029 entry.GetStat(&statbuf); 2030 2031 if (S_ISDIR(statbuf.st_mode)) { 2032 BDirectory subdir(&entry); 2033 (*dirCount)++; 2034 (*running_size) += 1024; 2035 status_t result; 2036 if ((result = FSRecursiveCalcSize(wind, &subdir, running_size, 2037 fileCount, dirCount)) != B_OK) 2038 return result; 2039 } else { 2040 (*fileCount)++; 2041 (*running_size) += statbuf.st_size + 1024; // Add to compensate 2042 // for attributes. 2043 } 2044 } 2045 return B_OK; 2046 } 2047 2048 2049 status_t 2050 CalcItemsAndSize(BObjectList<entry_ref> *refList, int32 *totalCount, off_t *totalSize) 2051 { 2052 int32 fileCount = 0; 2053 int32 dirCount = 0; 2054 2055 thread_id tid = find_thread(NULL); 2056 2057 int32 num_items = refList->CountItems(); 2058 for (int32 i = 0; i < num_items; i++) { 2059 entry_ref *ref = refList->ItemAt(i); 2060 BEntry entry(ref); 2061 StatStruct statbuf; 2062 entry.GetStat(&statbuf); 2063 2064 if (gStatusWindow && gStatusWindow->CheckCanceledOrPaused(tid)) 2065 return kUserCanceled; 2066 2067 if (S_ISDIR(statbuf.st_mode)) { 2068 BDirectory dir(&entry); 2069 dirCount++; 2070 (*totalSize) += 1024; 2071 status_t result; 2072 if ((result = FSRecursiveCalcSize(NULL, &dir, totalSize, &fileCount, 2073 &dirCount)) != B_OK) 2074 return result; 2075 } else { 2076 fileCount++; 2077 (*totalSize) += statbuf.st_size + 1024; 2078 } 2079 } 2080 2081 *totalCount += (fileCount + dirCount); 2082 return B_OK; 2083 } 2084 2085 2086 status_t 2087 FSGetTrashDir(BDirectory *trash_dir, dev_t dev) 2088 { 2089 2090 BVolume volume(dev); 2091 status_t result = volume.InitCheck(); 2092 if (result != B_OK) 2093 return result; 2094 2095 BPath path; 2096 result = find_directory(B_TRASH_DIRECTORY, &path, true, &volume); 2097 if (result != B_OK) 2098 return result; 2099 2100 result = trash_dir->SetTo(path.Path()); 2101 if (result != B_OK) 2102 return result; 2103 2104 // make trash invisible 2105 attr_info a_info; 2106 if (trash_dir->GetAttrInfo(kAttrPoseInfo, &a_info) != B_OK) { 2107 2108 StatStruct sbuf; 2109 trash_dir->GetStat(&sbuf); 2110 2111 // move trash to bottom left of main screen initially 2112 BScreen screen(B_MAIN_SCREEN_ID); 2113 BRect scrn_frame = screen.Frame(); 2114 2115 PoseInfo poseInfo; 2116 poseInfo.fInvisible = false; 2117 poseInfo.fInitedDirectory = sbuf.st_ino; 2118 poseInfo.fLocation = BPoint(scrn_frame.left + 20, scrn_frame.bottom - 60); 2119 trash_dir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, 2120 sizeof(PoseInfo)); 2121 } 2122 2123 return B_OK; 2124 } 2125 2126 2127 status_t 2128 FSGetDeskDir(BDirectory *deskDir, dev_t dev) 2129 { 2130 BVolume volume(dev); 2131 status_t result = volume.InitCheck(); 2132 if (result != B_OK) 2133 return result; 2134 2135 BPath path; 2136 result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume); 2137 if (result != B_OK) 2138 return result; 2139 2140 result = deskDir->SetTo(path.Path()); 2141 if (result != B_OK) 2142 return result; 2143 2144 // make desktop invisible 2145 PoseInfo poseInfo; 2146 poseInfo.fInvisible = true; 2147 poseInfo.fInitedDirectory = -1LL; 2148 deskDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, sizeof(PoseInfo)); 2149 2150 size_t size; 2151 const void* data = GetTrackerResources()-> 2152 LoadResource('ICON', kResDeskIcon, &size); 2153 if (data != NULL) 2154 deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size); 2155 2156 data = GetTrackerResources()-> 2157 LoadResource('MICN', kResDeskIcon, &size); 2158 if (data != NULL) 2159 deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size); 2160 2161 #ifdef __HAIKU__ 2162 data = GetTrackerResources()-> 2163 LoadResource(B_VECTOR_ICON_TYPE, kResDeskIcon, &size); 2164 if (data != NULL) 2165 deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size); 2166 #endif 2167 2168 return B_OK; 2169 } 2170 2171 2172 status_t 2173 FSGetBootDeskDir(BDirectory *deskDir) 2174 { 2175 BVolume bootVol; 2176 BVolumeRoster().GetBootVolume(&bootVol); 2177 BPath path; 2178 2179 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &bootVol); 2180 if (result != B_OK) 2181 return result; 2182 2183 return deskDir->SetTo(path.Path()); 2184 } 2185 2186 2187 static bool 2188 FSIsDirFlavor(const BEntry *entry, directory_which directoryType) 2189 { 2190 StatStruct dir_stat; 2191 StatStruct entry_stat; 2192 BVolume volume; 2193 BPath path; 2194 2195 if (entry->GetStat(&entry_stat) != B_OK) 2196 return false; 2197 2198 if (volume.SetTo(entry_stat.st_dev) != B_OK) 2199 return false; 2200 2201 if (find_directory(directoryType, &path, false, &volume) != B_OK) 2202 return false; 2203 2204 stat(path.Path(), &dir_stat); 2205 2206 return dir_stat.st_ino == entry_stat.st_ino 2207 && dir_stat.st_dev == entry_stat.st_dev; 2208 } 2209 2210 2211 bool 2212 FSIsPrintersDir(const BEntry *entry) 2213 { 2214 return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY); 2215 } 2216 2217 2218 bool 2219 FSIsTrashDir(const BEntry *entry) 2220 { 2221 return FSIsDirFlavor(entry, B_TRASH_DIRECTORY); 2222 } 2223 2224 2225 bool 2226 FSIsDeskDir(const BEntry *entry, dev_t device) 2227 { 2228 BVolume volume(device); 2229 status_t result = volume.InitCheck(); 2230 if (result != B_OK) 2231 return false; 2232 2233 BPath path; 2234 result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume); 2235 if (result != B_OK) 2236 return false; 2237 2238 BEntry entryToCompare(path.Path()); 2239 return entryToCompare == *entry; 2240 } 2241 2242 2243 bool 2244 FSIsDeskDir(const BEntry *entry) 2245 { 2246 entry_ref ref; 2247 if (entry->GetRef(&ref) != B_OK) 2248 return false; 2249 2250 return FSIsDeskDir(entry, ref.device); 2251 } 2252 2253 2254 bool 2255 FSIsSystemDir(const BEntry *entry) 2256 { 2257 return FSIsDirFlavor(entry, B_BEOS_SYSTEM_DIRECTORY); 2258 } 2259 2260 2261 bool 2262 FSIsBeOSDir(const BEntry *entry) 2263 { 2264 return FSIsDirFlavor(entry, B_BEOS_DIRECTORY); 2265 } 2266 2267 2268 bool 2269 FSIsHomeDir(const BEntry *entry) 2270 { 2271 return FSIsDirFlavor(entry, B_USER_DIRECTORY); 2272 } 2273 2274 2275 bool 2276 DirectoryMatchesOrContains(const BEntry *entry, directory_which which) 2277 { 2278 BPath path; 2279 if (find_directory(which, &path, false, NULL) != B_OK) 2280 return false; 2281 2282 BEntry dirEntry(path.Path()); 2283 if (dirEntry.InitCheck() != B_OK) 2284 return false; 2285 2286 if (dirEntry == *entry) 2287 // root level match 2288 return true; 2289 2290 BDirectory dir(&dirEntry); 2291 return dir.Contains(entry); 2292 } 2293 2294 2295 bool 2296 DirectoryMatchesOrContains(const BEntry *entry, const char *additionalPath, 2297 directory_which which) 2298 { 2299 BPath path; 2300 if (find_directory(which, &path, false, NULL) != B_OK) 2301 return false; 2302 2303 path.Append(additionalPath); 2304 BEntry dirEntry(path.Path()); 2305 if (dirEntry.InitCheck() != B_OK) 2306 return false; 2307 2308 if (dirEntry == *entry) 2309 // root level match 2310 return true; 2311 2312 BDirectory dir(&dirEntry); 2313 return dir.Contains(entry); 2314 } 2315 2316 2317 bool 2318 DirectoryMatches(const BEntry *entry, directory_which which) 2319 { 2320 BPath path; 2321 if (find_directory(which, &path, false, NULL) != B_OK) 2322 return false; 2323 2324 BEntry dirEntry(path.Path()); 2325 if (dirEntry.InitCheck() != B_OK) 2326 return false; 2327 2328 return dirEntry == *entry; 2329 } 2330 2331 2332 bool 2333 DirectoryMatches(const BEntry *entry, const char *additionalPath, directory_which which) 2334 { 2335 BPath path; 2336 if (find_directory(which, &path, false, NULL) != B_OK) 2337 return false; 2338 2339 path.Append(additionalPath); 2340 BEntry dirEntry(path.Path()); 2341 if (dirEntry.InitCheck() != B_OK) 2342 return false; 2343 2344 return dirEntry == *entry; 2345 } 2346 2347 2348 extern status_t 2349 FSFindTrackerSettingsDir(BPath *path, bool autoCreate) 2350 { 2351 status_t result = find_directory (B_USER_SETTINGS_DIRECTORY, path, autoCreate); 2352 if (result != B_OK) 2353 return result; 2354 2355 path->Append("Tracker"); 2356 2357 return mkdir(path->Path(), 0777) ? B_OK : errno; 2358 } 2359 2360 2361 bool 2362 FSInTrashDir(const entry_ref *ref) 2363 { 2364 BEntry entry(ref); 2365 if (entry.InitCheck() != B_OK) 2366 return false; 2367 2368 BDirectory trashDir; 2369 if (FSGetTrashDir(&trashDir, ref->device) != B_OK) 2370 return false; 2371 2372 return trashDir.Contains(&entry); 2373 } 2374 2375 2376 void 2377 FSEmptyTrash() 2378 { 2379 if (find_thread("_tracker_empty_trash_") != B_OK) { 2380 resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_", 2381 B_NORMAL_PRIORITY, NULL)); 2382 } 2383 } 2384 2385 2386 status_t 2387 empty_trash(void *) 2388 { 2389 // empty trash on all mounted volumes 2390 status_t err = B_OK; 2391 2392 thread_id thread = find_thread(NULL); 2393 if (gStatusWindow) 2394 gStatusWindow->CreateStatusItem(thread, kTrashState); 2395 2396 // calculate the sum total of all items on all volumes in trash 2397 BObjectList<entry_ref> srcList; 2398 int32 totalCount = 0; 2399 off_t totalSize = 0; 2400 2401 BVolumeRoster volumeRoster; 2402 BVolume volume; 2403 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 2404 if (volume.IsReadOnly() || !volume.IsPersistent()) 2405 continue; 2406 2407 BDirectory trashDirectory; 2408 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK) 2409 continue; 2410 2411 BEntry entry; 2412 trashDirectory.GetEntry(&entry); 2413 2414 entry_ref ref; 2415 entry.GetRef(&ref); 2416 srcList.AddItem(&ref); 2417 err = CalcItemsAndSize(&srcList, &totalCount, &totalSize); 2418 if (err != B_OK) 2419 break; 2420 2421 srcList.MakeEmpty(); 2422 2423 // don't count trash directory itself 2424 totalCount--; 2425 } 2426 2427 if (err == B_OK) { 2428 if (gStatusWindow) 2429 gStatusWindow->InitStatusItem(thread, totalCount, totalCount); 2430 2431 volumeRoster.Rewind(); 2432 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 2433 TrackerCopyLoopControl loopControl(thread); 2434 2435 if (volume.IsReadOnly() || !volume.IsPersistent()) 2436 continue; 2437 2438 BDirectory trashDirectory; 2439 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK) 2440 continue; 2441 2442 BEntry entry; 2443 trashDirectory.GetEntry(&entry); 2444 err = FSDeleteFolder(&entry, &loopControl, true, false); 2445 } 2446 } 2447 2448 if (err != B_OK && err != kTrashCanceled && err != kUserCanceled) { 2449 (new BAlert("", "Error emptying Trash!", "OK", NULL, NULL, 2450 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 2451 } 2452 2453 if (gStatusWindow) 2454 gStatusWindow->RemoveStatusItem(find_thread(NULL)); 2455 2456 return B_OK; 2457 } 2458 2459 2460 status_t 2461 _DeleteTask(BObjectList<entry_ref> *list, bool confirm) 2462 { 2463 if (confirm) { 2464 bool dontMoveToTrash = TrackerSettings().DontMoveFilesToTrash(); 2465 2466 if (!dontMoveToTrash) { 2467 BAlert *alert = new BAlert("", kDeleteConfirmationStr, 2468 "Cancel", "Move to Trash", "Delete", B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 2469 B_WARNING_ALERT); 2470 2471 alert->SetShortcut(0, B_ESCAPE); 2472 alert->SetShortcut(1, 'm'); 2473 alert->SetShortcut(2, 'd'); 2474 2475 switch (alert->Go()) { 2476 case 0: 2477 delete list; 2478 return B_OK; 2479 case 1: 2480 FSMoveToTrash(list, NULL, false); 2481 return B_OK; 2482 } 2483 } else { 2484 BAlert *alert = new BAlert("", kDeleteConfirmationStr, 2485 "Cancel", "Delete", NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 2486 B_WARNING_ALERT); 2487 2488 alert->SetShortcut(0, B_ESCAPE); 2489 alert->SetShortcut(1, 'd'); 2490 2491 if (!alert->Go()) { 2492 delete list; 2493 return B_OK; 2494 } 2495 } 2496 } 2497 2498 thread_id thread = find_thread(NULL); 2499 if (gStatusWindow) 2500 gStatusWindow->CreateStatusItem(thread, kDeleteState); 2501 2502 // calculate the sum total of all items on all volumes in trash 2503 int32 totalItems = 0; 2504 int64 totalSize = 0; 2505 2506 status_t err = CalcItemsAndSize(list, &totalItems, &totalSize); 2507 if (err == B_OK) { 2508 if (gStatusWindow) 2509 gStatusWindow->InitStatusItem(thread, totalItems, totalItems); 2510 2511 int32 count = list->CountItems(); 2512 TrackerCopyLoopControl loopControl(thread); 2513 for (int32 index = 0; index < count; index++) { 2514 entry_ref ref(*list->ItemAt(index)); 2515 BEntry entry(&ref); 2516 loopControl.UpdateStatus(ref.name, ref, 1, true); 2517 if (entry.IsDirectory()) 2518 err = FSDeleteFolder(&entry, &loopControl, true, true, true); 2519 else 2520 err = entry.Remove(); 2521 } 2522 2523 if (err != kTrashCanceled && err != kUserCanceled && err != B_OK) 2524 (new BAlert("", "Error Deleting items", "OK", NULL, NULL, 2525 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 2526 } 2527 if (gStatusWindow) 2528 gStatusWindow->RemoveStatusItem(find_thread(NULL)); 2529 2530 delete list; 2531 2532 return B_OK; 2533 } 2534 2535 status_t 2536 FSRecursiveCreateFolder(BPath path) 2537 { 2538 BEntry entry(path.Path()); 2539 if (entry.InitCheck() != B_OK) { 2540 BPath parentPath; 2541 status_t err = path.GetParent(&parentPath); 2542 if (err != B_OK) 2543 return err; 2544 2545 err = FSRecursiveCreateFolder(parentPath); 2546 if (err != B_OK) 2547 return err; 2548 } 2549 2550 entry.SetTo(path.Path()); 2551 if (entry.Exists()) 2552 return B_FILE_EXISTS; 2553 else { 2554 char name[B_FILE_NAME_LENGTH]; 2555 BDirectory parent; 2556 2557 entry.GetParent(&parent); 2558 entry.GetName(name); 2559 parent.CreateDirectory(name, NULL); 2560 } 2561 2562 return B_OK; 2563 } 2564 2565 status_t 2566 _RestoreTask(BObjectList<entry_ref> *list) 2567 { 2568 thread_id thread = find_thread(NULL); 2569 if (gStatusWindow) 2570 gStatusWindow->CreateStatusItem(thread, kRestoreFromTrashState); 2571 2572 // calculate the sum total of all items that will be restored 2573 int32 totalItems = 0; 2574 int64 totalSize = 0; 2575 2576 status_t err = CalcItemsAndSize(list, &totalItems, &totalSize); 2577 if (err == B_OK) { 2578 if (gStatusWindow) 2579 gStatusWindow->InitStatusItem(thread, totalItems, totalItems); 2580 2581 int32 count = list->CountItems(); 2582 TrackerCopyLoopControl loopControl(thread); 2583 for (int32 index = 0; index < count; index++) { 2584 entry_ref ref(*list->ItemAt(index)); 2585 BEntry entry(&ref); 2586 BPath originalPath; 2587 2588 loopControl.UpdateStatus(ref.name, ref, 1, true); 2589 2590 if (FSGetOriginalPath(&entry, &originalPath) != B_OK) 2591 continue; 2592 2593 BEntry originalEntry(originalPath.Path()); 2594 BPath parentPath; 2595 err = originalPath.GetParent(&parentPath); 2596 if (err != B_OK) 2597 continue; 2598 BEntry parentEntry(parentPath.Path()); 2599 2600 if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) { 2601 if (FSRecursiveCreateFolder(parentPath) == B_OK) { 2602 originalEntry.SetTo(originalPath.Path()); 2603 if (entry.InitCheck() != B_OK) 2604 continue; 2605 } 2606 } 2607 2608 if (!originalEntry.Exists()) { 2609 BDirectory dir(parentPath.Path()); 2610 if (dir.InitCheck() == B_OK) { 2611 char leafName[B_FILE_NAME_LENGTH]; 2612 originalEntry.GetName(leafName); 2613 if (entry.MoveTo(&dir, leafName) == B_OK) { 2614 BNode node(&entry); 2615 if (node.InitCheck() == B_OK) 2616 node.RemoveAttr(kAttrOriginalPath); 2617 } 2618 } 2619 } 2620 2621 err = loopControl.CheckUserCanceled(); 2622 if (err != B_OK) 2623 break; 2624 } 2625 } 2626 if (gStatusWindow) 2627 gStatusWindow->RemoveStatusItem(find_thread(NULL)); 2628 2629 delete list; 2630 2631 return err; 2632 } 2633 2634 void 2635 FSCreateTrashDirs() 2636 { 2637 BVolume volume; 2638 BVolumeRoster roster; 2639 2640 roster.Rewind(); 2641 while (roster.GetNextVolume(&volume) == B_OK) { 2642 if (volume.IsReadOnly() || !volume.IsPersistent()) 2643 continue; 2644 2645 BPath path; 2646 find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume); 2647 find_directory(B_TRASH_DIRECTORY, &path, true, &volume); 2648 2649 BDirectory trashDir; 2650 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) { 2651 size_t size; 2652 const void* data = GetTrackerResources()-> 2653 LoadResource('ICON', kResTrashIcon, &size); 2654 if (data != NULL) { 2655 trashDir.WriteAttr(kAttrLargeIcon, 'ICON', 0, 2656 data, size); 2657 } 2658 data = GetTrackerResources()-> 2659 LoadResource('MICN', kResTrashIcon, &size); 2660 if (data != NULL) { 2661 trashDir.WriteAttr(kAttrMiniIcon, 'MICN', 0, 2662 data, size); 2663 } 2664 #ifdef __HAIKU 2665 data = GetTrackerResources()-> 2666 LoadResource(B_VECTOR_ICON_TYPE, kResTrashIcon, &size); 2667 if (data != NULL) { 2668 trashDir.WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, 2669 data, size); 2670 } 2671 #endif 2672 } 2673 } 2674 } 2675 2676 2677 status_t 2678 FSCreateNewFolder(const entry_ref *ref) 2679 { 2680 node_ref node; 2681 node.device = ref->device; 2682 node.node = ref->directory; 2683 2684 BDirectory dir(&node); 2685 status_t result = dir.InitCheck(); 2686 if (result != B_OK) 2687 return result; 2688 2689 // ToDo: is that really necessary here? 2690 BString name(ref->name); 2691 FSMakeOriginalName(name, &dir, "-"); 2692 2693 BDirectory newDir; 2694 result = dir.CreateDirectory(name.String(), &newDir); 2695 if (result != B_OK) 2696 return result; 2697 2698 BNodeInfo nodeInfo(&newDir); 2699 nodeInfo.SetType(B_DIR_MIMETYPE); 2700 2701 return result; 2702 } 2703 2704 2705 status_t 2706 FSCreateNewFolderIn(const node_ref *dirNode, entry_ref *newRef, 2707 node_ref *newNode) 2708 { 2709 BDirectory dir(dirNode); 2710 status_t result = dir.InitCheck(); 2711 if (result == B_OK) { 2712 char name[B_FILE_NAME_LENGTH]; 2713 strcpy(name, "New Folder"); 2714 2715 int32 fnum = 1; 2716 while (dir.Contains(name)) { 2717 // if base name already exists then add a number 2718 // ToDo: 2719 // move this logic ot FSMakeOriginalName 2720 if (++fnum > 9) 2721 sprintf(name, "New Folder%ld", fnum); 2722 else 2723 sprintf(name, "New Folder %ld", fnum); 2724 } 2725 2726 BDirectory newDir; 2727 result = dir.CreateDirectory(name, &newDir); 2728 if (result == B_OK) { 2729 BEntry entry; 2730 newDir.GetEntry(&entry); 2731 entry.GetRef(newRef); 2732 entry.GetNodeRef(newNode); 2733 2734 BNodeInfo nodeInfo(&newDir); 2735 nodeInfo.SetType(B_DIR_MIMETYPE); 2736 2737 // add undo item 2738 NewFolderUndo undo(*newRef); 2739 return B_OK; 2740 } 2741 } 2742 2743 (new BAlert("", "Sorry, could not create a new folder.", "Cancel", 0, 0, 2744 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 2745 return result; 2746 } 2747 2748 2749 ReadAttrResult 2750 ReadAttr(const BNode *node, const char *hostAttrName, const char *foreignAttrName, 2751 type_code type, off_t offset, void *buffer, size_t length, 2752 void (*swapFunc)(void *), bool isForeign) 2753 { 2754 if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer, length) == (ssize_t)length) 2755 return kReadAttrNativeOK; 2756 2757 // PRINT(("trying %s\n", foreignAttrName)); 2758 // try the other endianness 2759 if (node->ReadAttr(foreignAttrName, type, offset, buffer, length) != (ssize_t)length) 2760 return kReadAttrFailed; 2761 2762 // PRINT(("got %s\n", foreignAttrName)); 2763 if (!swapFunc) 2764 return kReadAttrForeignOK; 2765 2766 (swapFunc)(buffer); 2767 // run the endian swapper 2768 2769 return kReadAttrForeignOK; 2770 } 2771 2772 2773 ReadAttrResult 2774 GetAttrInfo(const BNode *node, const char *hostAttrName, const char *foreignAttrName, 2775 type_code *type, size_t *size) 2776 { 2777 attr_info info; 2778 2779 if (node->GetAttrInfo(hostAttrName, &info) == B_OK) { 2780 if (type) 2781 *type = info.type; 2782 if (size) 2783 *size = (size_t)info.size; 2784 2785 return kReadAttrNativeOK; 2786 } 2787 2788 if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) { 2789 if (type) 2790 *type = info.type; 2791 if (size) 2792 *size = (size_t)info.size; 2793 2794 return kReadAttrForeignOK; 2795 } 2796 return kReadAttrFailed; 2797 } 2798 2799 // launching code 2800 2801 static status_t 2802 TrackerOpenWith(const BMessage *refs) 2803 { 2804 BMessage clone(*refs); 2805 ASSERT(dynamic_cast<TTracker *>(be_app)); 2806 ASSERT(clone.what); 2807 clone.AddInt32("launchUsingSelector", 0); 2808 // runs the Open With window 2809 be_app->PostMessage(&clone); 2810 2811 return B_OK; 2812 } 2813 2814 static void 2815 AsynchLaunchBinder(void (*func)(const entry_ref *, const BMessage *, bool on), 2816 const entry_ref *entry, const BMessage *message, bool on) 2817 { 2818 Thread::Launch(NewFunctionObject(func, entry, message, on), 2819 B_NORMAL_PRIORITY, "LaunchTask"); 2820 } 2821 2822 static bool 2823 SniffIfGeneric(const entry_ref *ref) 2824 { 2825 BNode node(ref); 2826 char type[B_MIME_TYPE_LENGTH]; 2827 BNodeInfo info(&node); 2828 if (info.GetType(type) == B_OK && strcasecmp(type, B_FILE_MIME_TYPE) != 0) 2829 // already has a type and it's not octet stream 2830 return false; 2831 2832 BPath path(ref); 2833 if (path.Path()) { 2834 // force a mimeset 2835 node.RemoveAttr(kAttrMIMEType); 2836 update_mime_info(path.Path(), 0, 1, 1); 2837 } 2838 2839 return true; 2840 } 2841 2842 static void 2843 SniffIfGeneric(const BMessage *refs) 2844 { 2845 entry_ref ref; 2846 for (int32 index = 0; ; index++) { 2847 if (refs->FindRef("refs", index, &ref) != B_OK) 2848 break; 2849 SniffIfGeneric(&ref); 2850 } 2851 } 2852 2853 static void 2854 _TrackerLaunchAppWithDocuments(const entry_ref *appRef, const BMessage *refs, bool openWithOK) 2855 { 2856 team_id team; 2857 2858 status_t error = B_ERROR; 2859 BString alertString; 2860 2861 for (int32 mimesetIt = 0; ; mimesetIt++) { 2862 error = be_roster->Launch(appRef, refs, &team); 2863 if (error == B_ALREADY_RUNNING) 2864 // app already running, not really an error 2865 error = B_OK; 2866 2867 if (error == B_OK) 2868 break; 2869 2870 if (mimesetIt > 0) 2871 break; 2872 2873 // failed to open, try mimesetting the refs and launching again 2874 SniffIfGeneric(refs); 2875 } 2876 2877 if (error == B_OK) { 2878 // close possible parent window, if specified 2879 const node_ref *nodeToClose = 0; 2880 int32 numBytes; 2881 refs->FindData("nodeRefsToClose", B_RAW_TYPE, (const void **)&nodeToClose, &numBytes); 2882 if (nodeToClose) 2883 dynamic_cast<TTracker *>(be_app)->CloseParent(*nodeToClose); 2884 } else { 2885 alertString << "Could not open \"" << appRef->name << "\" (" << strerror(error) << "). "; 2886 if (refs && openWithOK) { 2887 alertString << kFindAlternativeStr; 2888 if ((new BAlert("", alertString.String(), "Cancel", "Find", 0, 2889 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1) 2890 error = TrackerOpenWith(refs); 2891 } else 2892 (new BAlert("", alertString.String(), "Cancel", 0, 0, 2893 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 2894 } 2895 } 2896 2897 extern "C" char** environ; 2898 2899 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 2900 extern "C" status_t _kern_load_image(int32 argCount, const char **args, 2901 int32 envCount, const char **env, int32 priority, uint32 flags, 2902 port_id errorPort, uint32 errorToken); 2903 #else 2904 extern "C" 2905 # if !B_BEOS_VERSION_DANO 2906 _IMPEXP_ROOT 2907 # endif 2908 status_t _kload_image_etc_(int argc, char **argv, char **envp, 2909 char *buf, int bufsize); 2910 #endif 2911 2912 2913 static status_t 2914 LoaderErrorDetails(const entry_ref *app, BString &details) 2915 { 2916 BPath path; 2917 BEntry appEntry(app, true); 2918 2919 status_t result = appEntry.GetPath(&path); 2920 if (result != B_OK) 2921 return result; 2922 2923 char *argv[2] = { const_cast<char *>(path.Path()), 0}; 2924 2925 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 2926 port_id errorPort = create_port(1, "Tracker loader error"); 2927 2928 // count environment variables 2929 uint32 envCount = 0; 2930 while (environ[envCount] != NULL) 2931 envCount++; 2932 2933 result = _kern_load_image(1, (const char **)argv, envCount, 2934 (const char **)environ, B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, 2935 errorPort, 0); 2936 if (result == B_OK) { 2937 // we weren't supposed to be able to start the application... 2938 return B_ERROR; 2939 } 2940 2941 // read error message from port and construct details string 2942 2943 ssize_t bufferSize; 2944 2945 do { 2946 bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0); 2947 } while (bufferSize == B_INTERRUPTED); 2948 2949 if (bufferSize <= B_OK) { 2950 delete_port(errorPort); 2951 return bufferSize; 2952 } 2953 2954 uint8 *buffer = (uint8 *)malloc(bufferSize); 2955 if (buffer == NULL) { 2956 delete_port(errorPort); 2957 return B_NO_MEMORY; 2958 } 2959 2960 bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize, 2961 B_RELATIVE_TIMEOUT, 0); 2962 delete_port(errorPort); 2963 2964 if (bufferSize < B_OK) { 2965 free(buffer); 2966 return bufferSize; 2967 } 2968 2969 BMessage message; 2970 result = message.Unflatten((const char *)buffer); 2971 free(buffer); 2972 2973 if (result != B_OK) 2974 return result; 2975 2976 const char* library; 2977 for (int32 i = 0; message.FindString("missing library", i, 2978 &library) == B_OK; i++) { 2979 if (i > 0) 2980 details += ", "; 2981 details += library; 2982 } 2983 2984 return B_OK; 2985 #else 2986 result = _kload_image_etc_(1, argv, environ, details.LockBuffer(1024), 1024); 2987 details.UnlockBuffer(); 2988 #endif 2989 return B_OK; 2990 } 2991 2992 2993 static void 2994 _TrackerLaunchDocuments(const entry_ref */*doNotUse*/, const BMessage *refs, 2995 bool openWithOK) 2996 { 2997 BMessage copyOfRefs(*refs); 2998 2999 entry_ref documentRef; 3000 if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) 3001 // nothing to launch, we are done 3002 return; 3003 3004 status_t error = B_ERROR; 3005 entry_ref app; 3006 BMessage *refsToPass = NULL; 3007 BString alertString; 3008 const char *alternative = 0; 3009 3010 for (int32 mimesetIt = 0; ; mimesetIt++) { 3011 alertString = ""; 3012 error = be_roster->FindApp(&documentRef, &app); 3013 3014 if (error != B_OK && mimesetIt == 0) { 3015 SniffIfGeneric(©OfRefs); 3016 continue; 3017 } 3018 3019 if (error != B_OK) { 3020 alertString << "Could not find an application to open \"" << documentRef.name 3021 << "\" (" << strerror(error) << "). "; 3022 if (openWithOK) 3023 alternative = kFindApplicationStr; 3024 3025 break; 3026 } else { 3027 BEntry appEntry(&app, true); 3028 for (int32 index = 0;;) { 3029 // remove the app itself from the refs received so we don't try 3030 // to open ourselves 3031 entry_ref ref; 3032 if (copyOfRefs.FindRef("refs", index, &ref) != B_OK) 3033 break; 3034 3035 // deal with symlinks properly 3036 BEntry documentEntry(&ref, true); 3037 if (appEntry == documentEntry) { 3038 PRINT(("stripping %s, app %s \n", ref.name, app.name)); 3039 copyOfRefs.RemoveData("refs", index); 3040 } else { 3041 PRINT(("leaving %s, app %s \n", ref.name, app.name)); 3042 index++; 3043 } 3044 } 3045 3046 refsToPass = CountRefs(©OfRefs) > 0 ? ©OfRefs: 0; 3047 team_id team; 3048 error = be_roster->Launch(&app, refsToPass, &team); 3049 if (error == B_ALREADY_RUNNING) 3050 // app already running, not really an error 3051 error = B_OK; 3052 if (error == B_OK || mimesetIt != 0) 3053 break; 3054 3055 SniffIfGeneric(©OfRefs); 3056 } 3057 } 3058 3059 if (error != B_OK && alertString.Length() == 0) { 3060 BString loaderErrorString; 3061 bool openedDocuments = true; 3062 3063 if (!refsToPass) { 3064 // we just double clicked the app itself, do not offer to 3065 // find a handling app 3066 openWithOK = false; 3067 openedDocuments = false; 3068 } 3069 3070 if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) { 3071 alertString << "Could not open \"" << app.name 3072 << "\". The file is mistakenly marked as executable. "; 3073 3074 if (!openWithOK) { 3075 // offer the possibility to change the permissions 3076 3077 alertString << "\nShould this be fixed?"; 3078 if ((new BAlert("", alertString.String(), "Cancel", "Proceed", 0, 3079 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1) { 3080 BEntry entry(&documentRef); 3081 mode_t permissions; 3082 3083 error = entry.GetPermissions(&permissions); 3084 if (error == B_OK) 3085 error = entry.SetPermissions(permissions & ~(S_IXUSR | S_IXGRP | S_IXOTH)); 3086 if (error == B_OK) { 3087 // we updated the permissions, so let's try again 3088 _TrackerLaunchDocuments(NULL, refs, false); 3089 return; 3090 } else { 3091 alertString = "Could not update permissions of file \""; 3092 alertString << app.name << "\". " << strerror(error); 3093 } 3094 } else 3095 return; 3096 } 3097 3098 alternative = kFindApplicationStr; 3099 } else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) { 3100 alertString << "Could not open \"" << documentRef.name 3101 << "\" because application \"" << app.name << "\" is in the trash. "; 3102 alternative = kFindAlternativeStr; 3103 } else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) { 3104 alertString << "Could not open \"" << documentRef.name << "\" " 3105 << "(" << strerror(error) << "). "; 3106 alternative = kFindAlternativeStr; 3107 } else if (error == B_MISSING_SYMBOL 3108 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { 3109 alertString << "Could not open \"" << documentRef.name << "\" "; 3110 if (openedDocuments) 3111 alertString << "with application \"" << app.name << "\" "; 3112 alertString << "(Missing symbol: " << loaderErrorString << "). \n"; 3113 alternative = kFindAlternativeStr; 3114 } else if (error == B_MISSING_LIBRARY 3115 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { 3116 alertString << "Could not open \"" << documentRef.name << "\" "; 3117 if (openedDocuments) 3118 alertString << "with application \"" << app.name << "\" "; 3119 alertString << "(Missing libraries: " << loaderErrorString << "). \n"; 3120 alternative = kFindAlternativeStr; 3121 } else { 3122 alertString << "Could not open \"" << documentRef.name 3123 << "\" with application \"" << app.name << "\" (" << strerror(error) << "). "; 3124 alternative = kFindAlternativeStr; 3125 } 3126 } 3127 3128 if (error != B_OK) { 3129 if (openWithOK) { 3130 ASSERT(alternative); 3131 alertString << alternative; 3132 if ((new BAlert("", alertString.String(), "Cancel", "Find", 0, 3133 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1) 3134 error = TrackerOpenWith(refs); 3135 } else 3136 (new BAlert("", alertString.String(), "Cancel", 0, 0, 3137 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 3138 } 3139 } 3140 3141 // the following three calls don't return any reasonable error codes, 3142 // should fix that, making them void 3143 3144 status_t 3145 TrackerLaunch(const entry_ref *appRef, const BMessage *refs, bool async, bool openWithOK) 3146 { 3147 if (!async) 3148 _TrackerLaunchAppWithDocuments(appRef, refs, openWithOK); 3149 else 3150 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs, openWithOK); 3151 3152 return B_OK; 3153 } 3154 3155 status_t 3156 TrackerLaunch(const entry_ref *appRef, bool async) 3157 { 3158 if (!async) 3159 _TrackerLaunchAppWithDocuments(appRef, 0, false); 3160 else 3161 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false); 3162 3163 return B_OK; 3164 } 3165 3166 status_t 3167 TrackerLaunch(const BMessage *refs, bool async, bool openWithOK) 3168 { 3169 if (!async) 3170 _TrackerLaunchDocuments(0, refs, openWithOK); 3171 else 3172 AsynchLaunchBinder(&_TrackerLaunchDocuments, 0, refs, openWithOK); 3173 3174 return B_OK; 3175 } 3176 3177 status_t 3178 LaunchBrokenLink(const char *signature, const BMessage *refs) 3179 { 3180 // This call is to support a hacky workaround for double-clicking 3181 // broken refs for cifs 3182 be_roster->Launch(signature, const_cast<BMessage *>(refs)); 3183 return B_OK; 3184 } 3185 3186 // external launch calls; need to be robust, work if Tracker is not running 3187 3188 #if !B_BEOS_VERSION_DANO 3189 _IMPEXP_TRACKER 3190 #endif 3191 status_t 3192 FSLaunchItem(const entry_ref *application, const BMessage *refsReceived, 3193 bool async, bool openWithOK) 3194 { 3195 return TrackerLaunch(application, refsReceived, async, openWithOK); 3196 } 3197 3198 3199 #if !B_BEOS_VERSION_DANO 3200 _IMPEXP_TRACKER 3201 #endif 3202 status_t 3203 FSOpenWith(BMessage *listOfRefs) 3204 { 3205 status_t result = B_ERROR; 3206 listOfRefs->what = B_REFS_RECEIVED; 3207 3208 if (dynamic_cast<TTracker *>(be_app)) 3209 result = TrackerOpenWith(listOfRefs); 3210 else 3211 ASSERT(!"not yet implemented"); 3212 3213 return result; 3214 } 3215 3216 // legacy calls, need for compatibility 3217 3218 void 3219 FSOpenWithDocuments(const entry_ref *executable, BMessage *documents) 3220 { 3221 TrackerLaunch(executable, documents, true); 3222 delete documents; 3223 } 3224 3225 status_t 3226 FSLaunchUsing(const entry_ref *ref, BMessage *listOfRefs) 3227 { 3228 BMessage temp(B_REFS_RECEIVED); 3229 if (!listOfRefs) { 3230 ASSERT(ref); 3231 temp.AddRef("refs", ref); 3232 listOfRefs = &temp; 3233 } 3234 FSOpenWith(listOfRefs); 3235 return B_OK; 3236 } 3237 3238 status_t 3239 FSLaunchItem(const entry_ref *ref, BMessage* message, int32, bool async) 3240 { 3241 if (message) 3242 message->what = B_REFS_RECEIVED; 3243 3244 status_t result = TrackerLaunch(ref, message, async, true); 3245 delete message; 3246 return result; 3247 } 3248 3249 3250 void 3251 FSLaunchItem(const entry_ref *ref, BMessage *message, int32 workspace) 3252 { 3253 FSLaunchItem(ref, message, workspace, true); 3254 } 3255 3256 // Get the original path of an entry in the trash 3257 status_t 3258 FSGetOriginalPath(BEntry *entry, BPath *result) 3259 { 3260 status_t err; 3261 entry_ref ref; 3262 err = entry->GetRef(&ref); 3263 if (err != B_OK) 3264 return err; 3265 3266 // Only call the routine for entries in the trash 3267 if (!FSInTrashDir(&ref)) 3268 return B_ERROR; 3269 3270 BNode node(entry); 3271 BString originalPath; 3272 if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) { 3273 // We're in luck, the entry has the original path in an attribute 3274 err = result->SetTo(originalPath.String()); 3275 return err; 3276 } 3277 3278 // Iterate the parent directories to find one with 3279 // the original path attribute 3280 BEntry parent(*entry); 3281 err = parent.InitCheck(); 3282 if (err != B_OK) 3283 return err; 3284 3285 // walk up the directory structure until we find a node 3286 // with original path attribute 3287 do { 3288 // move to the parent of this node 3289 err = parent.GetParent(&parent); 3290 if (err != B_OK) 3291 return err; 3292 3293 // return if we are at the root of the trash 3294 if (FSIsTrashDir(&parent)) 3295 return B_ENTRY_NOT_FOUND; 3296 3297 // get the parent as a node 3298 err = node.SetTo(&parent); 3299 if (err != B_OK) 3300 return err; 3301 } while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK); 3302 3303 // Found the attribute, figure out there this file 3304 // used to live, based on the successfully-read attribute 3305 err = result->SetTo(originalPath.String()); 3306 if (err != B_OK) 3307 return err; 3308 3309 BPath path, pathParent; 3310 err = parent.GetPath(&pathParent); 3311 if (err != B_OK) 3312 return err; 3313 err = entry->GetPath(&path); 3314 if (err != B_OK) 3315 return err; 3316 result->Append(path.Path() + strlen(pathParent.Path()) + 1); 3317 // compute the new path by appending the offset of 3318 // the item we are locating, to the original path 3319 // of the parent 3320 return B_OK; 3321 } 3322 3323 directory_which 3324 WellKnowEntryList::Match(const node_ref *node) 3325 { 3326 const WellKnownEntry *result = MatchEntry(node); 3327 if (result) 3328 return result->which; 3329 3330 return (directory_which)-1; 3331 } 3332 3333 const WellKnowEntryList::WellKnownEntry * 3334 WellKnowEntryList::MatchEntry(const node_ref *node) 3335 { 3336 if (!self) 3337 self = new WellKnowEntryList(); 3338 3339 return self->MatchEntryCommon(node); 3340 } 3341 3342 const WellKnowEntryList::WellKnownEntry * 3343 WellKnowEntryList::MatchEntryCommon(const node_ref *node) 3344 { 3345 uint32 count = entries.size(); 3346 for (uint32 index = 0; index < count; index++) 3347 if (*node == entries[index].node) 3348 return &entries[index]; 3349 3350 return NULL; 3351 } 3352 3353 3354 void 3355 WellKnowEntryList::Quit() 3356 { 3357 delete self; 3358 self = NULL; 3359 } 3360 3361 void 3362 WellKnowEntryList::AddOne(directory_which which, const char *name) 3363 { 3364 BPath path; 3365 if (find_directory(which, &path, true) != B_OK) 3366 return; 3367 3368 BEntry entry(path.Path()); 3369 node_ref node; 3370 if (entry.GetNodeRef(&node) != B_OK) 3371 return; 3372 3373 entries.push_back(WellKnownEntry(&node, which, name)); 3374 } 3375 3376 void 3377 WellKnowEntryList::AddOne(directory_which which, directory_which base, 3378 const char *extra, const char *name) 3379 { 3380 BPath path; 3381 if (find_directory(base, &path, true) != B_OK) 3382 return; 3383 3384 path.Append(extra); 3385 BEntry entry(path.Path()); 3386 node_ref node; 3387 if (entry.GetNodeRef(&node) != B_OK) 3388 return; 3389 3390 entries.push_back(WellKnownEntry(&node, which, name)); 3391 } 3392 3393 void 3394 WellKnowEntryList::AddOne(directory_which which, const char *path, const char *name) 3395 { 3396 BEntry entry(path); 3397 node_ref node; 3398 if (entry.GetNodeRef(&node) != B_OK) 3399 return; 3400 3401 entries.push_back(WellKnownEntry(&node, which, name)); 3402 } 3403 3404 3405 WellKnowEntryList::WellKnowEntryList() 3406 { 3407 AddOne(B_BEOS_DIRECTORY, "beos"); 3408 AddOne((directory_which)B_BOOT_DISK, "/boot", "boot"); 3409 AddOne(B_USER_DIRECTORY, "home"); 3410 AddOne(B_BEOS_SYSTEM_DIRECTORY, "system"); 3411 3412 AddOne(B_BEOS_FONTS_DIRECTORY, "fonts"); 3413 AddOne(B_COMMON_FONTS_DIRECTORY, "fonts"); 3414 AddOne(B_USER_FONTS_DIRECTORY, "fonts"); 3415 3416 AddOne(B_BEOS_APPS_DIRECTORY, "apps"); 3417 AddOne(B_APPS_DIRECTORY, "apps"); 3418 AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY, B_USER_DESKBAR_DIRECTORY, 3419 "Applications", "apps"); 3420 3421 AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences"); 3422 AddOne(B_PREFERENCES_DIRECTORY, "preferences"); 3423 AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY, B_USER_DESKBAR_DIRECTORY, 3424 "Preferences", "preferences"); 3425 3426 AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail", "mail"); 3427 3428 AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY, "queries", "queries"); 3429 3430 3431 3432 AddOne(B_COMMON_DEVELOP_DIRECTORY, "develop"); 3433 AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY, B_USER_DESKBAR_DIRECTORY, 3434 "Development", "develop"); 3435 3436 AddOne(B_USER_CONFIG_DIRECTORY, "config"); 3437 3438 AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY, "people", "people"); 3439 3440 AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY, "Downloads", 3441 "Downloads"); 3442 } 3443 3444 WellKnowEntryList *WellKnowEntryList::self = NULL; 3445 3446 } 3447