1 /* 2 * Copyright (c) 2008 Stephan Aßmus <superstippi@gmx.de>. All rights reserved. 3 * Distributed under the terms of the MIT/X11 license. 4 * 5 * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software 6 * as long as it is accompanied by it's documentation and this copyright notice. 7 * The software comes with no warranty, etc. 8 */ 9 10 11 #include "Scanner.h" 12 13 #include <stdlib.h> 14 #include <string.h> 15 16 #include <Directory.h> 17 18 #include "Common.h" 19 20 using std::vector; 21 22 23 Scanner::Scanner(BVolume *v, BHandler *handler) 24 : 25 BLooper(), 26 fListener(handler), 27 fDoneMessage(kScanDone), 28 fProgressMessage(kScanProgress), 29 fVolume(v), 30 fSnapshot(NULL), 31 fDesiredPath(), 32 fTask(), 33 fBusy(false), 34 fQuitRequested(false) 35 { 36 fDoneMessage.AddPointer(kNameVolPtr, fVolume); 37 fProgressMessage.AddPointer(kNameVolPtr, fVolume); 38 39 Run(); 40 } 41 42 43 Scanner::~Scanner() 44 { 45 delete fSnapshot; 46 } 47 48 49 void 50 Scanner::MessageReceived(BMessage* message) 51 { 52 switch (message->what) { 53 case kScanRefresh: 54 { 55 FileInfo* startInfo; 56 if (message->FindPointer(kNameFilePtr, (void **)&startInfo) == B_OK) 57 _RunScan(startInfo); 58 break; 59 } 60 61 default: 62 BLooper::MessageReceived(message); 63 break; 64 } 65 } 66 67 68 void 69 Scanner::Refresh(FileInfo* startInfo) 70 { 71 if (fBusy) 72 return; 73 74 fBusy = true; 75 76 // Remember the current directory, if any, so we can return to it after 77 // the scanning is done. 78 if (fSnapshot != NULL && fSnapshot->currentDir != NULL) 79 fSnapshot->currentDir->GetPath(fDesiredPath); 80 81 fTask.assign(kEmptyStr); 82 83 BMessage msg(kScanRefresh); 84 msg.AddPointer(kNameFilePtr, startInfo); 85 PostMessage(&msg); 86 } 87 88 89 void 90 Scanner::SetDesiredPath(string &path) 91 { 92 fDesiredPath = path; 93 94 if (fSnapshot == NULL) 95 Refresh(); 96 else 97 _ChangeToDesired(); 98 } 99 100 101 void 102 Scanner::RequestQuit() 103 { 104 // If the looper thread is scanning, we won't succeed to grab the lock, 105 // since the thread is scanning from within MessageReceived(). Then by 106 // setting fQuitRequested, the thread will stop scanning and only after 107 // that happened we succeed to grab the lock. So this line of action 108 // should be safe. 109 fQuitRequested = true; 110 Lock(); 111 Quit(); 112 } 113 114 115 // #pragma mark - private 116 117 118 void 119 Scanner::_RunScan(FileInfo* startInfo) 120 { 121 if (startInfo == NULL || startInfo == fSnapshot->rootDir) { 122 delete fSnapshot; 123 fSnapshot = new VolumeSnapshot(fVolume); 124 fTask = kStrScanningX + fSnapshot->name; 125 fVolumeBytesInUse = fSnapshot->capacity - fSnapshot->freeBytes; 126 fVolumeBytesScanned = 0; 127 fProgress = 0.0; 128 fLastReport = -100.0; 129 130 BDirectory root; 131 fVolume->GetRootDirectory(&root); 132 fSnapshot->rootDir = _GetFileInfo(&root, NULL); 133 134 FileInfo* freeSpace = new FileInfo; 135 freeSpace->pseudo = true; 136 string s = "Free on " + fSnapshot->name; 137 freeSpace->ref.set_name(s.c_str()); 138 freeSpace->size = fSnapshot->freeBytes; 139 fSnapshot->freeSpace = freeSpace; 140 141 fSnapshot->currentDir = NULL; 142 143 } else { 144 fSnapshot->capacity = fVolume->Capacity(); 145 fSnapshot->freeBytes = fVolume->FreeBytes(); 146 fTask = kStrScanningX + string(startInfo->ref.name); 147 fVolumeBytesInUse = fSnapshot->capacity - fSnapshot->freeBytes; 148 fVolumeBytesScanned = fVolumeBytesInUse - startInfo->size; // best guess 149 fProgress = 100.0 * fVolumeBytesScanned / fVolumeBytesInUse; 150 fLastReport = -100.0; 151 152 BDirectory startDir(&startInfo->ref); 153 if (startDir.InitCheck() == B_OK) { 154 FileInfo *parent = startInfo->parent; 155 vector<FileInfo *>::iterator i = parent->children.begin(); 156 FileInfo* newInfo = _GetFileInfo(&startDir, parent); 157 while (i != parent->children.end() && *i != startInfo) 158 i++; 159 160 int idx = i - parent->children.begin(); 161 parent->children[idx] = newInfo; 162 163 // Fixup count and size fields in parent directory. 164 parent->size += newInfo->size - startInfo->size; 165 parent->count += newInfo->count - startInfo->count; 166 167 delete startInfo; 168 } 169 } 170 171 fBusy = false; 172 _ChangeToDesired(); 173 fListener.SendMessage(&fDoneMessage); 174 } 175 176 177 FileInfo* 178 Scanner::_GetFileInfo(BDirectory* dir, FileInfo* parent) 179 { 180 FileInfo* thisDir = new FileInfo; 181 BEntry entry; 182 dir->GetEntry(&entry); 183 entry.GetRef(&thisDir->ref); 184 thisDir->parent = parent; 185 thisDir->count = 0; 186 187 while (true) { 188 if (fQuitRequested) 189 break; 190 191 if (dir->GetNextEntry(&entry) == B_ENTRY_NOT_FOUND) 192 break; 193 if (entry.IsSymLink()) 194 continue; 195 196 if (entry.IsFile()) { 197 FileInfo *child = new FileInfo; 198 entry.GetRef(&child->ref); 199 entry.GetSize(&child->size); 200 child->parent = thisDir; 201 child->color = -1; 202 thisDir->children.push_back(child); 203 204 // Send a progress report periodically. 205 fVolumeBytesScanned += child->size; 206 fProgress = 100.0 * fVolumeBytesScanned / fVolumeBytesInUse; 207 if (fProgress - fLastReport > kReportInterval) { 208 fLastReport = fProgress; 209 fListener.SendMessage(&fProgressMessage); 210 } 211 } 212 else if (entry.IsDirectory()) { 213 BDirectory childDir(&entry); 214 thisDir->children.push_back(_GetFileInfo(&childDir, thisDir)); 215 } 216 thisDir->count++; 217 } 218 219 vector<FileInfo *>::iterator i = thisDir->children.begin(); 220 while (i != thisDir->children.end()) { 221 thisDir->size += (*i)->size; 222 thisDir->count += (*i)->count; 223 i++; 224 } 225 226 return thisDir; 227 } 228 229 230 void 231 Scanner::_ChangeToDesired() 232 { 233 if (fBusy || fDesiredPath.size() <= 0) 234 return; 235 236 char* workPath = strdup(fDesiredPath.c_str()); 237 char* pathPtr = &workPath[1]; // skip leading '/' 238 char* endOfPath = pathPtr + strlen(pathPtr); 239 240 // Check the name of the root directory. It is a special case because 241 // it has no parent data structure. 242 FileInfo* checkDir = fSnapshot->rootDir; 243 FileInfo* prefDir = NULL; 244 char* sep = strchr(pathPtr, '/'); 245 if (sep != NULL) 246 *sep = '\0'; 247 248 if (strcmp(pathPtr, checkDir->ref.name) == 0) { 249 // Root directory matches, so follow the remainder of the path as 250 // far as possible. 251 prefDir = checkDir; 252 pathPtr += 1 + strlen(pathPtr); 253 while (pathPtr < endOfPath) { 254 sep = strchr(pathPtr, '/'); 255 if (sep != NULL) 256 *sep = '\0'; 257 258 checkDir = prefDir->FindChild(pathPtr); 259 if (checkDir == NULL || checkDir->children.size() == 0) 260 break; 261 262 pathPtr += 1 + strlen(pathPtr); 263 prefDir = checkDir; 264 } 265 } 266 267 // If the desired path is the volume's root directory, default to the 268 // volume display. 269 if (prefDir == fSnapshot->rootDir) 270 prefDir = NULL; 271 272 ChangeDir(prefDir); 273 274 free(workPath); 275 fDesiredPath.erase(); 276 } 277