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