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