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