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