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 <Catalog.h> 17 #include <Directory.h> 18 19 #include "DiskUsage.h" 20 21 #undef B_TRANSLATION_CONTEXT 22 #define B_TRANSLATION_CONTEXT "Scanner" 23 24 using std::vector; 25 26 27 Scanner::Scanner(BVolume *v, BHandler *handler) 28 : 29 BLooper(), 30 fListener(handler), 31 fDoneMessage(kScanDone), 32 fProgressMessage(kScanProgress), 33 fVolume(v), 34 fSnapshot(NULL), 35 fDesiredPath(), 36 fTask(), 37 fBusy(false), 38 fQuitRequested(false) 39 { 40 Run(); 41 } 42 43 44 Scanner::~Scanner() 45 { 46 delete fSnapshot; 47 } 48 49 50 void 51 Scanner::MessageReceived(BMessage* message) 52 { 53 switch (message->what) { 54 case kScanRefresh: 55 { 56 FileInfo* startInfo; 57 if (message->FindPointer(kNameFilePtr, (void **)&startInfo) 58 == B_OK) 59 _RunScan(startInfo); 60 break; 61 } 62 63 default: 64 BLooper::MessageReceived(message); 65 break; 66 } 67 } 68 69 70 void 71 Scanner::Refresh(FileInfo* startInfo) 72 { 73 if (fBusy) 74 return; 75 76 fBusy = true; 77 78 // Remember the current directory, if any, so we can return to it after 79 // the scanning is done. 80 if (fSnapshot != NULL && fSnapshot->currentDir != NULL) 81 fSnapshot->currentDir->GetPath(fDesiredPath); 82 83 fTask.assign(kEmptyStr); 84 85 BMessage msg(kScanRefresh); 86 msg.AddPointer(kNameFilePtr, startInfo); 87 PostMessage(&msg); 88 } 89 90 91 void 92 Scanner::Cancel() 93 { 94 if (!fBusy) 95 return; 96 97 fQuitRequested = true; 98 } 99 100 101 void 102 Scanner::SetDesiredPath(string &path) 103 { 104 fDesiredPath = path; 105 106 if (fSnapshot == NULL) 107 Refresh(); 108 else 109 _ChangeToDesired(); 110 } 111 112 113 void 114 Scanner::RequestQuit() 115 { 116 // If the looper thread is scanning, we won't succeed to grab the lock, 117 // since the thread is scanning from within MessageReceived(). Then by 118 // setting fQuitRequested, the thread will stop scanning and only after 119 // that happened we succeed to grab the lock. So this line of action 120 // should be safe. 121 fQuitRequested = true; 122 Lock(); 123 Quit(); 124 } 125 126 127 // #pragma mark - private 128 129 130 bool 131 Scanner::_DirectoryContains(FileInfo* currentDir, entry_ref* ref) 132 { 133 vector<FileInfo*>::iterator i = currentDir->children.begin(); 134 bool contains = currentDir->ref == *ref; 135 while (!contains && i != currentDir->children.end()) { 136 contains |= _DirectoryContains((*i), ref); 137 i++; 138 } 139 return contains; 140 } 141 142 143 void 144 Scanner::_RunScan(FileInfo* startInfo) 145 { 146 fQuitRequested = false; 147 BString stringScan(B_TRANSLATE("Scanning %refName%")); 148 149 if (startInfo == NULL || startInfo == fSnapshot->rootDir) { 150 VolumeSnapshot* previousSnapshot = fSnapshot; 151 fSnapshot = new VolumeSnapshot(fVolume); 152 stringScan.ReplaceFirst("%refName%", fSnapshot->name.c_str()); 153 fTask = stringScan.String(); 154 fVolumeBytesInUse = fSnapshot->capacity - fSnapshot->freeBytes; 155 fVolumeBytesScanned = 0; 156 fProgress = 0.0; 157 fLastReport = -1.0; 158 159 BDirectory root; 160 fVolume->GetRootDirectory(&root); 161 fSnapshot->rootDir = _GetFileInfo(&root, NULL); 162 if (fSnapshot->rootDir == NULL) { 163 delete fSnapshot; 164 fSnapshot = previousSnapshot; 165 fBusy = false; 166 fListener.SendMessage(&fDoneMessage); 167 return; 168 } 169 FileInfo* freeSpace = new FileInfo; 170 freeSpace->pseudo = true; 171 BString string(B_TRANSLATE("Free on %refName%")); 172 string.ReplaceFirst("%refName%", fSnapshot->name.c_str()); 173 freeSpace->ref.set_name(string.String()); 174 freeSpace->size = fSnapshot->freeBytes; 175 fSnapshot->freeSpace = freeSpace; 176 177 fSnapshot->currentDir = NULL; 178 179 delete previousSnapshot; 180 } else { 181 off_t previousVolumeCapacity = fSnapshot->capacity; 182 off_t previousVolumeFreeBytes = fSnapshot->freeBytes; 183 fSnapshot->capacity = fVolume->Capacity(); 184 fSnapshot->freeBytes = fVolume->FreeBytes(); 185 stringScan.ReplaceFirst("%refName%", startInfo->ref.name); 186 fTask = stringScan.String(); 187 fVolumeBytesInUse = fSnapshot->capacity - fSnapshot->freeBytes; 188 fVolumeBytesScanned = fVolumeBytesInUse - startInfo->size; //best guess 189 fProgress = fVolumeBytesScanned / fVolumeBytesInUse; 190 fLastReport = -1.0; 191 192 BDirectory startDir(&startInfo->ref); 193 if (startDir.InitCheck() == B_OK) { 194 FileInfo *parent = startInfo->parent; 195 vector<FileInfo *>::iterator i = parent->children.begin(); 196 FileInfo* newInfo = _GetFileInfo(&startDir, parent); 197 if (newInfo == NULL) { 198 fSnapshot->capacity = previousVolumeCapacity; 199 fSnapshot->freeBytes = previousVolumeFreeBytes; 200 fBusy = false; 201 fListener.SendMessage(&fDoneMessage); 202 return; 203 } 204 while (i != parent->children.end() && *i != startInfo) 205 i++; 206 207 int idx = i - parent->children.begin(); 208 parent->children[idx] = newInfo; 209 210 // Fixup count and size fields in parent directory. 211 off_t sizeDiff = newInfo->size - startInfo->size; 212 off_t countDiff = newInfo->count - startInfo->count; 213 while (parent != NULL) { 214 parent->size += sizeDiff; 215 parent->count += countDiff; 216 parent = parent->parent; 217 } 218 219 delete startInfo; 220 } 221 } 222 fBusy = false; 223 _ChangeToDesired(); 224 fListener.SendMessage(&fDoneMessage); 225 } 226 227 228 FileInfo* 229 Scanner::_GetFileInfo(BDirectory* dir, FileInfo* parent) 230 { 231 FileInfo* thisDir = new FileInfo; 232 BEntry entry; 233 dir->GetEntry(&entry); 234 entry.GetRef(&thisDir->ref); 235 thisDir->parent = parent; 236 thisDir->count = 0; 237 238 while (true) { 239 if (fQuitRequested) { 240 delete thisDir; 241 return NULL; 242 } 243 244 if (dir->GetNextEntry(&entry) == B_ENTRY_NOT_FOUND) 245 break; 246 if (entry.IsSymLink()) 247 continue; 248 249 250 if (entry.IsFile()) { 251 entry_ref ref; 252 if ((entry.GetRef(&ref) == B_OK) && (ref.device != Device())) 253 continue; 254 FileInfo *child = new FileInfo; 255 entry.GetRef(&child->ref); 256 entry.GetSize(&child->size); 257 child->parent = thisDir; 258 child->color = -1; 259 thisDir->children.push_back(child); 260 261 // Send a progress report periodically. 262 fVolumeBytesScanned += child->size; 263 fProgress = (float)fVolumeBytesScanned / fVolumeBytesInUse; 264 if (fProgress - fLastReport > kReportInterval) { 265 fLastReport = fProgress; 266 fListener.SendMessage(&fProgressMessage); 267 } 268 } 269 else if (entry.IsDirectory()) { 270 BDirectory childDir(&entry); 271 thisDir->children.push_back(_GetFileInfo(&childDir, thisDir)); 272 } 273 thisDir->count++; 274 } 275 276 vector<FileInfo *>::iterator i = thisDir->children.begin(); 277 while (i != thisDir->children.end()) { 278 thisDir->size += (*i)->size; 279 thisDir->count += (*i)->count; 280 i++; 281 } 282 283 return thisDir; 284 } 285 286 287 void 288 Scanner::_ChangeToDesired() 289 { 290 if (fBusy || fDesiredPath.size() <= 0) 291 return; 292 293 char* workPath = strdup(fDesiredPath.c_str()); 294 char* pathPtr = &workPath[1]; // skip leading '/' 295 char* endOfPath = pathPtr + strlen(pathPtr); 296 297 // Check the name of the root directory. It is a special case because 298 // it has no parent data structure. 299 FileInfo* checkDir = fSnapshot->rootDir; 300 FileInfo* prefDir = NULL; 301 char* sep = strchr(pathPtr, '/'); 302 if (sep != NULL) 303 *sep = '\0'; 304 305 if (strcmp(pathPtr, checkDir->ref.name) == 0) { 306 // Root directory matches, so follow the remainder of the path as 307 // far as possible. 308 prefDir = checkDir; 309 pathPtr += 1 + strlen(pathPtr); 310 while (pathPtr < endOfPath) { 311 sep = strchr(pathPtr, '/'); 312 if (sep != NULL) 313 *sep = '\0'; 314 315 checkDir = prefDir->FindChild(pathPtr); 316 if (checkDir == NULL || checkDir->children.size() == 0) 317 break; 318 319 pathPtr += 1 + strlen(pathPtr); 320 prefDir = checkDir; 321 } 322 } 323 324 // If the desired path is the volume's root directory, default to the 325 // volume display. 326 if (prefDir == fSnapshot->rootDir) 327 prefDir = NULL; 328 329 ChangeDir(prefDir); 330 331 free(workPath); 332 fDesiredPath.erase(); 333 } 334