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