/Application/Core/FilesystemManager.cs
C# | 1932 lines | 1123 code | 244 blank | 565 comment | 299 complexity | 3d6145905b877a842b719db9350f4666 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
1/** 2 * FilesystemManager.cs 3 * 4 * Handles all interaction with the filesystem such as scanning, 5 * writing and reading the disk as well as watching for changes. 6 * 7 * * * * * * * * * 8 * 9 * Copyright 2012 Simplare 10 * 11 * This code is part of the Stoffi Music Player Project. 12 * Visit our website at: stoffiplayer.com 13 * 14 * This program is free software; you can redistribute it and/or 15 * modify it under the terms of the GNU General Public License 16 * as published by the Free Software Foundation; either version 17 * 3 of the License, or (at your option) any later version. 18 * 19 * See stoffiplayer.com/license for more information. 20 **/ 21 22using Microsoft.WindowsAPICodePack.Shell; 23using System; 24using System.Collections; 25using System.Collections.Generic; 26using System.Collections.ObjectModel; 27using System.ComponentModel; 28using System.IO; 29using System.Linq; 30using System.Security.AccessControl; 31using System.Text; 32using System.Threading; 33using Un4seen.Bass; 34using Un4seen.Bass.AddOn.Tags; 35 36namespace Stoffi 37{ 38 /// <summary> 39 /// Represents a manager that takes care of talking to the filesystem 40 /// </summary> 41 public static class FilesystemManager 42 { 43 #region Members 44 45 private static List<FileSystemWatcher> janitors = new List<FileSystemWatcher>(); 46 private static Timer janitorLazyness = null; 47 private static List<JanitorTask> janitorTasks = new List<JanitorTask>(); 48 private static Thread scanThread = null; 49 private static string librariesPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\AppData\Roaming\Microsoft\Windows\Libraries\"; 50 private static List<SourceTree> sourceForest = new List<SourceTree>(); 51 52 #endregion 53 54 #region Properties 55 /// <summary> 56 /// Gets or sets the indicator that the scanner will listen to in order to know if it 57 /// should die gracefully. To be used when the application wants to close. 58 /// </summary> 59 public static bool ProgramIsClosed { get; set; } 60 61 /// <summary> 62 /// Gets whether the manager has been initialized 63 /// </summary> 64 public static bool IsInitialized { get; private set; } 65 66 /// <summary> 67 /// Gets the current status of the scanner 68 /// </summary> 69 public static bool IsScanning 70 { 71 get { return scanThread.IsAlive; } 72 } 73 74 #endregion 75 76 #region Constructor 77 78 /// <summary> 79 /// Creates a filesystem manager 80 /// </summary> 81 static FilesystemManager() 82 { 83 IsInitialized = false; 84 } 85 86 #endregion 87 88 #region Methods 89 90 #region Public 91 92 /// <summary> 93 /// Initializes the filesystem manager by setting up library watchers and event handlers 94 /// </summary> 95 public static void Initialize() 96 { 97 ProgramIsClosed = false; 98 99 // setup a janitor for libraries 100 FileSystemWatcher fsw = new FileSystemWatcher(); 101 fsw.Path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\AppData\Roaming\Microsoft\Windows\Libraries\"; 102 fsw.Filter = "*.library-ms"; 103 fsw.IncludeSubdirectories = false; 104 fsw.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Security | NotifyFilters.Size; 105 fsw.Changed += Janitor_LibraryChanged; 106 fsw.Created += Janitor_LibraryChanged; 107 fsw.Deleted += Janitor_LibraryChanged; 108 fsw.Renamed += Janitor_LibraryRenamed; 109 fsw.EnableRaisingEvents = true; 110 janitors.Add(fsw); 111 112 // setup event handler to check if track changes 113 foreach (TrackData t in SettingsManager.FileTracks) 114 if (t != null) 115 t.PropertyChanged += new PropertyChangedEventHandler(Track_PropertyChanged); 116 foreach (TrackData t in SettingsManager.HistoryTracks) 117 if (t != null) 118 t.PropertyChanged += new PropertyChangedEventHandler(Track_PropertyChanged); 119 foreach (TrackData t in SettingsManager.QueueTracks) 120 if (t != null) 121 t.PropertyChanged += new PropertyChangedEventHandler(Track_PropertyChanged); 122 foreach (PlaylistData p in SettingsManager.Playlists) 123 foreach (TrackData t in p.Tracks) 124 if (t != null) 125 t.PropertyChanged += new PropertyChangedEventHandler(Track_PropertyChanged); 126 127 IsInitialized = true; 128 } 129 130 /// <summary> 131 /// Get a specific track 132 /// </summary> 133 /// <param name="filename">The filename of the track</param> 134 /// <returns>The TrackData for the track</returns> 135 public static TrackData GetTrack(String filename) 136 { 137 foreach (TrackData t in SettingsManager.FileTracks) 138 if (t.Path == filename) 139 return t; 140 return null; 141 } 142 143 /// <summary> 144 /// Writes the current track data to the file 145 /// </summary> 146 /// <param name="track">The track to save</param> 147 public static void SaveTrack(TrackData track) 148 { 149 TagLib.File file = TagLib.File.Create(track.Path); 150 file.Tag.Performers = track.Artist.Split(','); 151 file.Tag.Album = track.Album; 152 file.Tag.Title = track.Title; 153 file.Tag.Year = Convert.ToUInt32(track.Year); 154 file.Tag.Genres = track.Genre.Split(','); 155 file.Tag.Track = Convert.ToUInt32(track.Track); 156 157 file.Save(); 158 UpdateTrack(track); 159 } 160 161 /// <summary> 162 /// Checks if the meta data of a track has been updated since last read. 163 /// </summary> 164 /// <param name="track">The track to check</param> 165 /// <returns>True if the track has been written to since last read</returns> 166 public static bool Updated(TrackData track) 167 { 168 try 169 { 170 FileInfo fInfo = new FileInfo(track.Path); 171 return fInfo.LastWriteTimeUtc.Ticks > track.LastWrite; 172 } 173 catch 174 { 175 return false; 176 } 177 } 178 179 /// <summary> 180 /// Reads the metadata of a track and updates it 181 /// </summary> 182 /// <param name="track">The track to update</param> 183 /// <param name="copy">Whether the data should be copied to queue/history/playlists</param> 184 public static void UpdateTrack(TrackData track, bool copy = true) 185 { 186 if (!Updated(track)) return; 187 188 try 189 { 190 FileInfo fInfo = new FileInfo(track.Path); 191 TagLib.File file = TagLib.File.Create(track.Path, TagLib.ReadStyle.Average); 192 193 track.Artist = U.CleanXMLString(file.Tag.JoinedPerformers); 194 track.Album = U.CleanXMLString(file.Tag.Album); 195 track.Title = U.CleanXMLString(file.Tag.Title); 196 track.Genre = U.CleanXMLString(file.Tag.JoinedGenres); 197 track.Track = file.Tag.Track; 198 track.Year = file.Tag.Year; 199 track.Length = U.TimeSpanToString(file.Properties.Duration); 200 track.RawLength = file.Properties.Duration.TotalSeconds; 201 track.LastWrite = fInfo.LastWriteTimeUtc.Ticks; 202 track.Bitrate = file.Properties.AudioBitrate; 203 track.Channels = file.Properties.AudioChannels; 204 track.SampleRate = file.Properties.AudioSampleRate; 205 track.Codecs = ""; 206 foreach (TagLib.ICodec c in file.Properties.Codecs) 207 track.Codecs += c.Description + ", "; 208 track.Codecs = track.Codecs.Substring(0, track.Codecs.Length - 2); 209 track.Processed = true; 210 } 211 catch (Exception e) 212 { 213 if (track.Artist == U.T("MetaDataLoading")) track.Artist = ""; 214 if (track.Album == U.T("MetaDataLoading")) track.Album = ""; 215 if (track.Title == U.T("MetaDataLoading")) track.Title = ""; 216 if (track.Genre == U.T("MetaDataLoading")) track.Genre = ""; 217 if (track.Length == U.T("MetaDataLoading")) track.Length = ""; 218 U.L(LogLevel.Warning, "FILESYSTEM", "Could not read ID3 data of file: " + track.Path); 219 U.L(LogLevel.Warning, "FILESYSTEM", e.Message); 220 } 221 222 track.Icon = "pack://application:,,,/Platform/Windows/GUI/Images/Icons/FileAudio.ico"; 223 224 if (!copy) return; 225 226 for (int i = 0; i < SettingsManager.FileTracks.Count; i++) 227 { 228 if (SettingsManager.FileTracks[i].Path == track.Path) 229 { 230 CopyTrackInfo(track, SettingsManager.FileTracks[i]); 231 break; 232 } 233 } 234 for (int i = 0; i < SettingsManager.QueueTracks.Count; i++) 235 { 236 if (SettingsManager.QueueTracks[i].Path == track.Path) 237 { 238 CopyTrackInfo(track, SettingsManager.QueueTracks[i]); 239 break; 240 } 241 } 242 for (int i = 0; i < SettingsManager.HistoryTracks.Count; i++) 243 { 244 if (SettingsManager.HistoryTracks[i].Path == track.Path) 245 { 246 CopyTrackInfo(track, SettingsManager.HistoryTracks[i]); 247 break; 248 } 249 } 250 foreach (PlaylistData p in SettingsManager.Playlists) 251 for (int i = 0; i < p.Tracks.Count; i++) 252 { 253 if (p.Tracks[i].Path == track.Path) 254 { 255 CopyTrackInfo(track, p.Tracks[i]); 256 break; 257 } 258 } 259 } 260 261 /// <summary> 262 /// Toggle a source between Include and Ignore 263 /// </summary> 264 /// <param name="source">The source to toggle</param> 265 public static void ToggleSource(SourceData source) 266 { 267 source.Include = !source.Include; 268 ScanSources(); 269 } 270 271 /// <summary> 272 /// Add a source to the collection 273 /// </summary> 274 /// <param name="path">The path of source to be added</param> 275 /// <param name="callback">A function that will be sent along with the SourceModified event</param> 276 /// <param name="callbackParams">The parameters for the callback function</param> 277 public static void AddSource(string path, ScannerCallback callback = null, object callbackParams = null) 278 { 279 if (File.Exists(path)) 280 { 281 AddSource(new SourceData() 282 { 283 Data = path, 284 Type = SourceType.File, 285 HumanType = U.T("SourcesTypeFile"), 286 Icon = "pack://application:,,,/Platform/Windows/GUI/Images/Icons/FileAudio.ico", 287 Include = true 288 }, callback, callbackParams); 289 } 290 else if (Directory.Exists(path)) 291 { 292 AddSource(new SourceData() 293 { 294 Data = path, 295 Type = SourceType.Folder, 296 HumanType = U.T("SourcesTypeFolder"), 297 Icon = "pack://application:,,,/Platform/Windows/GUI/Images/Icons/Folder.ico", 298 Include = true 299 }, callback, callbackParams); 300 } 301 else 302 { 303 AddSource(new SourceData() 304 { 305 Data = path, 306 Type = SourceType.Library, 307 HumanType = U.T("SourcesTypeLibrary"), 308 Icon = "pack://application:,,,/Platform/Windows/GUI/Images/Icons/Library.ico", 309 Include = true 310 }, callback, callbackParams); 311 } 312 } 313 314 /// <summary> 315 /// Add a source to the collection 316 /// </summary> 317 /// <param name="source">The source to be added</param> 318 /// <param name="callback">A function that will be sent along with the SourceModified event</param> 319 /// <param name="callbackParams">The parameters for the callback function</param> 320 public static void AddSource(SourceData source, ScannerCallback callback = null, object callbackParams = null) 321 { 322 U.L(LogLevel.Information, "FILESYSTEM", String.Format("{0} the {1} {2}", 323 (source.Include ? "Adding" : "Ignoring"), source.Type, source.Data)); 324 325 SourceTree s = GetSourceTree(source.Data, sourceForest); 326 if (s != null) 327 { 328 U.L(LogLevel.Warning, "FILESYSTEM", String.Format("Removing currently {0} {1} {2}", 329 (source.Include ? "added" : "ignored"), source.Type, source.Data)); 330 RemoveSource(source); 331 } 332 DispatchSourceAdded(source); 333 ScanSources(callback, callbackParams); 334 } 335 336 /// <summary> 337 /// Remove a source from the collection 338 /// </summary> 339 /// <param name="source">The source to be removed</param> 340 public static void RemoveSource(SourceData source) 341 { 342 if (!SettingsManager.Sources.Contains(source)) 343 { 344 U.L(LogLevel.Warning, "FILESYSTEM", "Trying to remove non-existing source " + source.Data); 345 return; 346 } 347 DispatchSourceRemoved(source); 348 ScanSources(); 349 } 350 351 /// <summary> 352 /// Retrieves a source given its data 353 /// </summary> 354 /// <param name="data">The data of the source</param> 355 /// <returns>A source matching criterion if found, otherwise null</returns> 356 public static SourceData GetSource(string data) 357 { 358 foreach (SourceData s in SettingsManager.Sources) 359 if (s.Data == data) 360 return s; 361 return null; 362 } 363 364 /// <summary> 365 /// Scans for Windows 7 Libraries of type Music and adds them to the collection. 366 /// </summary> 367 /// <param name="ScanSourcesWhenDone">Set to true to scan the folders afterwards</param> 368 /// <param name="ScanInBackground">Set to true to perform the scan in a background thread</param> 369 public static void ScanLibraries(bool ScanSourcesWhenDone = false, bool ScanInBackground = true) 370 { 371 WaitForInitialization(); 372 373 if (ScanInBackground) 374 { 375 ThreadStart ScanThread = delegate() 376 { 377 ScanLibrariesFunc(ScanSourcesWhenDone); 378 }; 379 380 Thread thread = new Thread(ScanThread); 381 thread.Name = "Scan libraries"; 382 thread.Priority = ThreadPriority.BelowNormal; 383 thread.Start(); 384 } 385 else 386 ScanLibrariesFunc(ScanSourcesWhenDone); 387 } 388 389 /// <summary> 390 /// Scan all folders in the collection for supported music tracks 391 /// </summary> 392 /// <param name="callback">A function that will be sent along with the SourceModified event</param> 393 /// <param name="callbackParams">The parameters for the callback function</param> 394 public static void ScanSources(ScannerCallback callback = null, object callbackParams = null) 395 { 396 WaitForInitialization(); 397 398 StopScan(); 399 400 ThreadStart ScanThread = delegate() 401 { 402 U.L(LogLevel.Information, "FILESYSTEM", "Starting scan of sources"); 403 DispatchProgressChanged(0, "start"); 404 405 U.L(LogLevel.Debug, "FILESYSTEM", "Refreshing meta data of existing tracks"); 406 foreach (TrackData t in SettingsManager.FileTracks) 407 { 408 if (Updated(t)) 409 DispatchSourceModified(t, SourceModificationType.Updated); 410 } 411 412 U.L(LogLevel.Debug, "FILESYSTEM", "Getting forest"); 413 List<SourceTree> forest = GetSourceForest(); 414 415 List<SourceTree> addedPaths = new List<SourceTree>(); 416 List<SourceTree> removedPaths = new List<SourceTree>(); 417 U.L(LogLevel.Debug, "FILESYSTEM", "Calculating forest change"); 418 GetForestDifference(sourceForest, forest, addedPaths, removedPaths); 419 420 //U.L(LogLevel.Debug, "FILESYSTEM", String.Format("Removing {0} paths ", removedPaths.Count)); 421 //foreach (SourceTree tree in removedPaths) 422 //{ 423 // bool added = PathIsAdded(tree.Data, forest); 424 // bool ignored = PathIsIgnored(tree.Data, forest); 425 426 // if ((tree.Include && added) || (tree.Ignore && (!added || ignored))) 427 // continue; 428 429 // // see if there's any other sources on the same drive 430 // bool remove = true; 431 // foreach (SourceTree t in forest) 432 // { 433 // if (Path.GetPathRoot(t.Data) == Path.GetPathRoot(tree.Data)) 434 // { 435 // remove = false; 436 // break; 437 // } 438 // } 439 // if (remove) 440 // RemoveJanitor(Path.GetPathRoot(tree.Data)); 441 // tree.Ignore = tree.Include; 442 // ScanPath(tree.Data, tree, true, callback, callbackParams); 443 //} 444 U.L(LogLevel.Debug, "FILESYSTEM", String.Format("Adding {0} paths", addedPaths.Count)); 445 foreach (SourceTree tree in addedPaths) 446 { 447 SetupJanitor(Path.GetPathRoot(tree.Data)); 448 ScanPath(tree.Data, tree, true, callback, callbackParams); 449 } 450 451 U.L(LogLevel.Debug, "FILESYSTEM", String.Format("Checking existing tracks")); 452 foreach (TrackData track in SettingsManager.FileTracks) 453 { 454 if (PathIsIgnored(track.Path) || !File.Exists(track.Path)) 455 { 456 RemoveFile(track.Path, callback, callbackParams); 457 } 458 } 459 460 if (addedPaths.Count == 0 && removedPaths.Count == 0 && callback != null) 461 callback(callbackParams); 462 463 sourceForest = forest; 464 465 List<TrackData> tracksToRemove = new List<TrackData>(); 466 foreach (TrackData t in SettingsManager.FileTracks) 467 { 468 // remove files that should not be in collection 469 if (!PathIsAdded(t.Path)) 470 tracksToRemove.Add(t); 471 } 472 473 if (tracksToRemove.Count > 0) 474 U.L(LogLevel.Debug, "FILESYSTEM", "Dispatching SourceModified for all removals"); 475 foreach (TrackData t in tracksToRemove) 476 DispatchSourceModified(t, SourceModificationType.Removed); 477 478 DispatchProgressChanged(100, "done"); 479 U.L(LogLevel.Information, "FILESYSTEM", "Finished scan of sources"); 480 }; 481 scanThread = new Thread(ScanThread); 482 scanThread.Name = "Scan thread"; 483 scanThread.Priority = ThreadPriority.Lowest; 484 485 // read the meta data 486 ThreadStart updateThread = delegate() 487 { 488 // TODO: speed this up! 489 490 // wait for scan thread 491 U.L(LogLevel.Debug, "FILESYSTEM", "Meta scanner is waiting for file scanner"); 492 while (SettingsManager.FileTracks.Count == 0 && scanThread.IsAlive) ; 493 494 for (int i = 0; i < SettingsManager.FileTracks.Count; i++) 495 { 496 // break if program is closing 497 if (ProgramIsClosed) break; 498 499 // wait for scan thread 500 while (i >= SettingsManager.FileTracks.Count - 1 && scanThread.IsAlive); 501 502 // update track 503 if (i < SettingsManager.FileTracks.Count) 504 FilesystemManager.UpdateTrack(SettingsManager.FileTracks[i]); 505 } 506 }; 507 Thread metathread = new Thread(updateThread); 508 metathread.Name = "Read Meta thread"; 509 metathread.Priority = ThreadPriority.Lowest; 510 511 if (scanThread.IsAlive) scanThread.Abort(); 512 scanThread.Start(); 513 //metathread.Start(); 514 } 515 516 /// <summary> 517 /// Stop the scanner 518 /// </summary> 519 public static void StopScan() 520 { 521 if (scanThread != null && scanThread.IsAlive) 522 scanThread.Abort(); 523 } 524 525 /// <summary> 526 /// Create a track given a filename 527 /// </summary> 528 /// <param name="filename">The filename of the track</param> 529 /// <param name="dispatchModified">Whether or not to dispatch that the track has been added</param> 530 /// <returns>The newly created track</returns> 531 public static TrackData CreateTrack(String filename, bool dispatchModified = true) 532 { 533 if (!MediaManager.IsSupported(filename)) 534 { 535 U.L(LogLevel.Warning, "FILESYSTEM", "Cannot create track " + filename + ": unsupported format"); 536 return null; 537 } 538 if (!File.Exists(filename)) 539 { 540 U.L(LogLevel.Warning, "FILESYSTEM", "Cannot create track " + filename + ": file does not exist"); 541 return null; 542 } 543 544 FileInfo fInfo = new FileInfo(filename); 545 TrackData track = new TrackData 546 { 547 Processed = false, 548 Artist = U.T("MetaDataLoading"), 549 Album = U.T("MetaDataLoading"), 550 Title = U.T("MetaDataLoading"), 551 Genre = U.T("MetaDataLoading"), 552 Year = 0, 553 Length = U.T("MetaDataLoading"), 554 RawLength = 0, 555 PlayCount = 0, 556 LastPlayed = "", 557 Track = 0, 558 Path = filename, 559 Bitrate = 0, 560 Channels = 0, 561 SampleRate = 0, 562 Codecs = "", 563 Number = 0, 564 Bookmarks = new List<double>(), 565 Source = "Library", 566 Icon = @"..\..\Platform\Windows\GUI\Images\Icons\FileAudio.ico", 567 LastWrite = 0 568 }; 569 track.PropertyChanged += new PropertyChangedEventHandler(Track_PropertyChanged); 570 if (dispatchModified) 571 DispatchSourceModified(track, SourceModificationType.Added); 572 return track; 573 } 574 575 /// <summary> 576 /// Check of a given path is being watched 577 /// </summary> 578 /// <param name="path">The path to check</param> 579 /// <returns>true if <paramref name="path"/> is being watched, otherwise false</returns> 580 public static bool PathIsAdded(String path) 581 { 582 return PathIsAdded(path, sourceForest); 583 } 584 585 /// <summary> 586 /// Check of a given path is being watched 587 /// </summary> 588 /// <param name="path">The path to check</param> 589 /// <param name="forest">The forest to check in</param> 590 /// <returns>true if <paramref name="path"/> is being watched, otherwise false</returns> 591 public static bool PathIsAdded(String path, List<SourceTree> forest) 592 { 593 SourceTree s = GetSourceTree(path, forest, false); 594 if (s == null) 595 return false; 596 else 597 return s.Include; 598 } 599 600 /// <summary> 601 /// Check of a given path is being ignored 602 /// </summary> 603 /// <param name="path">The path to check</param> 604 /// <returns>true if <paramref name="path"/> is being ignored, otherwise false</returns> 605 public static bool PathIsIgnored(String path) 606 { 607 return PathIsIgnored(path, sourceForest); 608 } 609 610 /// <summary> 611 /// Check of a given path is being ignored 612 /// </summary> 613 /// <param name="path">The path to check</param> 614 /// <param name="forest">The forest to check in</param> 615 /// <returns>true if <paramref name="path"/> is being ignored, otherwise false</returns> 616 public static bool PathIsIgnored(String path, List<SourceTree> forest) 617 { 618 SourceTree s = GetSourceTree(path, forest, false); 619 if (s == null) return false; 620 else return s.Ignore; 621 } 622 623 /// <summary> 624 /// Checks if a track has been added to a collection of tracks. 625 /// </summary> 626 /// <param name="path">The path to check for</param> 627 /// <param name="tracks">The collection to check in</param> 628 /// <returns>True if a track with the same path is found</returns> 629 public static bool TrackIsAdded(string path, ObservableCollection<TrackData> tracks) 630 { 631 foreach (TrackData t in tracks) 632 if (t.Path == path) 633 return true; 634 return false; 635 } 636 637 /// <summary> 638 /// Checks if a track has been added to the file collection. 639 /// </summary> 640 /// <param name="path">The path to check for</param> 641 /// <returns>True if a track with the same path is found</returns> 642 public static bool TrackIsAdded(string path) 643 { 644 return TrackIsAdded(path, SettingsManager.FileTracks); 645 } 646 647 #endregion 648 649 #region Private 650 651 /// <summary> 652 /// Pauses until both the filesystem manager and media manager has been initialized 653 /// </summary> 654 private static void WaitForInitialization() 655 { 656 if (!MediaManager.IsInitialized) 657 { 658 U.L(LogLevel.Debug, "FILESYSTEM", "Waiting for Media Manager to be initialized, pausing scanner"); 659 while (!MediaManager.IsInitialized) ; 660 U.L(LogLevel.Debug, "FILESYSTEM", "Media Manager has been detected as initialized, resuming scanner"); 661 } 662 663 if (!IsInitialized) 664 { 665 U.L(LogLevel.Debug, "FILESYSTEM", "Waiting for Filesystem Manager to be initialized, pausing scanner"); 666 while (!IsInitialized) ; 667 U.L(LogLevel.Debug, "FILESYSTEM", "Filesystem Manager has been detected as initialized, resuming scanner"); 668 } 669 } 670 671 /// <summary> 672 /// Scan a path for files and synchronize its content with our collection 673 /// </summary> 674 /// <param name="path">The path to scan</param> 675 /// <param name="tree">The tree that is either the path itself or the parent</param> 676 /// <param name="progressPosition">The initial progress position when the scan starts</param> 677 /// <param name="deltaProgress">The amount of progress (in percentage points) that this scan will amount for</param> 678 /// <param name="removeIgnored">Whether or not we should remove any paths that we find which is set to ignore</param> 679 /// <param name="callback">A function that will be sent along with the SourceModified event</param> 680 /// <param name="callbackParams">The parameters for the callback function</param> 681 private static void ScanPath(String path, SourceTree tree, bool removeIgnored = true, ScannerCallback callback = null, object callbackParams = null) 682 { 683 // ignore the recycle bin 684 if (path.Substring(1).ToUpper().StartsWith(@":\$RECYCLE.BIN")) return; 685 686 // break if program is closing 687 if (ProgramIsClosed) return; 688 689 WaitForInitialization(); 690 691 // path is a file 692 if (File.Exists(path)) 693 { 694 if (tree.Include) AddFile(path, false, callback, callbackParams); 695 else if (removeIgnored) RemoveFile(path, callback, callbackParams); 696 } 697 698 // path is a folder 699 else if (Directory.Exists(path)) 700 { 701 string[] files = Directory.GetFiles(path); 702 string[] folders = Directory.GetDirectories(path); 703 704 // calculate the amount of progress to increase each file/folder with 705 //double progressIncrease = deltaProgress / (files.Count() + folders.Count()); 706 707 // scan all toplevel files 708 string[] ext = MediaManager.SupportedFormatsExtensions.Split(';'); 709 try 710 { 711 for (int i=0; i<files.Count(); i++) 712 { 713 // break if program is closing 714 if (ProgramIsClosed) return; 715 716 string file = files[i]; 717 718 //int progressPos = (int)(progressPosition + (i * progressIncrease)); 719 //DispatchProgressChanged(progressPos, "progress"); 720 721 if (!ext.Contains("*" + Path.GetExtension(file))) 722 continue; 723 724 bool include = tree.Include; 725 SourceTree st = GetSourceTree(file, tree.Children); 726 if (st != null) 727 include = st.Include; 728 729 foreach (SourceTree childTree in tree.Children) 730 { 731 if (childTree.Data == file) 732 { 733 include = childTree.Include; 734 break; 735 } 736 } 737 if (include) AddFile(file, false, callback, callbackParams); 738 else if (removeIgnored) RemoveFile(file, callback, callbackParams); 739 } 740 741 // dive inside subfolders 742 for (int i=0; i<folders.Count(); i++) 743 { 744 // break if program is closing 745 if (ProgramIsClosed) return; 746 747 string subfolder = folders[i]; 748 749 //int progressPos = (int)(progressPosition + ((files.Count() + i) * progressIncrease)); 750 //DispatchProgressChanged(progressPos, "progress"); 751 752 SourceTree subtree = GetSourceTree(subfolder, tree.Children); 753 if (subtree == null) subtree = tree; 754 ScanPath(subfolder, subtree, removeIgnored, callback, callbackParams); 755 } 756 } 757 catch (Exception e) 758 { 759 // break if program is closing 760 if (ProgramIsClosed) return; 761 762 U.L(LogLevel.Warning, "FILESYSTEM", "Could not scan " + path + ": " + e.Message); 763 } 764 } 765 } 766 767 /// <summary> 768 /// Copy all metadata from one track to another 769 /// </summary> 770 /// <param name="source">The source track from which the metadata is copied</param> 771 /// <param name="destination">The destination track to which the metadata is copied</param> 772 private static void CopyTrackInfo(TrackData source, TrackData destination) 773 { 774 destination.Artist = source.Artist; 775 destination.Album = source.Album; 776 destination.Genre = source.Genre; 777 destination.Title = source.Title; 778 destination.Track = source.Track; 779 destination.Year = source.Year; 780 destination.Length = source.Length; 781 destination.RawLength = source.RawLength; 782 destination.LastWrite = source.LastWrite; 783 destination.Bitrate = source.Bitrate; 784 destination.RawViews = source.RawViews; 785 destination.Icon = source.Icon; 786 destination.Codecs = source.Codecs; 787 destination.Channels = source.Channels; 788 destination.SampleRate = source.SampleRate; 789 } 790 791 /// <summary> 792 /// Add a file to the collection 793 /// </summary> 794 /// <param name="filename">The path of the file</param> 795 /// <param name="scanMetaData">Whether to scan the tracks meta data as well</param> 796 /// <param name="callback">A function that will be sent along with the SourceModified event</param> 797 /// <param name="callbackParams">The parameters for the callback function</param> 798 private static void AddFile(String filename, bool scanMetaData = false, ScannerCallback callback = null, object callbackParams = null) 799 { 800 //ThreadStart addThread = delegate() 801 //{ 802 //check if the file is already added 803 //if (TrackIsAdded(filename)) 804 //{ 805 // if (callback != null) 806 // callback(callbackParams); 807 // return; 808 //} 809 810 FileInfo fInfo = new FileInfo(filename); 811 TrackData track = new TrackData 812 { 813 Processed = false, 814 Artist = U.T("MetaDataLoading"), 815 Album = U.T("MetaDataLoading"), 816 Title = U.T("MetaDataLoading"), 817 Genre = U.T("MetaDataLoading"), 818 Year = 0, 819 Length = U.T("MetaDataLoading"), 820 RawLength = 0, 821 PlayCount = 0, 822 LastPlayed = "", 823 Bitrate = 0, 824 Channels = 0, 825 SampleRate = 0, 826 Codecs = "", 827 Track = 0, 828 Path = fInfo.FullName, 829 Number = 0, 830 Icon = @"..\..\Platform\Windows\GUI\Images\Icons\FileAudio.ico", 831 LastWrite = 0 832 }; 833 834 DispatchSourceModified(track, SourceModificationType.Added, callback, callbackParams); 835 track.PropertyChanged += new PropertyChangedEventHandler(Track_PropertyChanged); 836 if (scanMetaData) 837 DispatchPathModified(track.Path); 838 //}; 839 //Thread addthread = new Thread(addThread); 840 //addthread.Name = "Add file thread"; 841 //addthread.Priority = ThreadPriority.Lowest; 842 //addthread.Start(); 843 } 844 845 /// <summary> 846 /// Remove a file from the collection 847 /// </summary> 848 /// <param name="filename">The path of the file</param> 849 /// <param name="callback">A function that will be sent along with the SourceModified event</param> 850 /// <param name="callbackParams">The parameters for the callback function</param> 851 private static void RemoveFile(String filename, ScannerCallback callback = null, object callbackParams = null) 852 { 853 //ThreadStart delThread = delegate() 854 //{ 855 TrackData track = null; 856 foreach (TrackData t in SettingsManager.FileTracks) 857 { 858 if (t.Path == filename) 859 { 860 track = t; 861 break; 862 } 863 } 864 if (track != null) 865 DispatchSourceModified(track, SourceModificationType.Removed, callback, callbackParams); 866 else if (callback != null) 867 callback(callbackParams); 868 //}; 869 //Thread delthread = new Thread(delThread); 870 //delthread.Name = "Remove file thread"; 871 //delthread.Priority = ThreadPriority.Lowest; 872 //delthread.Start(); 873 } 874 875 /// <summary> 876 /// Changes the path of a set of tracks 877 /// </summary> 878 /// <param name="oldName">The current path of the tracks</param> 879 /// <param name="newName">The new path of the tracks</param> 880 /// <param name="tracks">The set of tracks</param> 881 private static void RenamePath(String oldName, String newName, ObservableCollection<TrackData> tracks) 882 { 883 foreach (TrackData track in tracks) 884 if (track.Path.StartsWith(oldName)) 885 track.Path = track.Path.Replace(oldName, newName); 886 } 887 888 /// <summary> 889 /// Gets a specific source tree given its data 890 /// </summary> 891 /// <param name="data">The data of the source tree</param> 892 /// <param name="forest">The parent of the source to look for</param> 893 /// <param name="exactMatch">Whether the path match should be exact or just initial</param> 894 /// <returns>The source with the corresponding data if one could be found, otherwise null</returns> 895 private static SourceTree GetSourceTree(String data, List<SourceTree> forest, bool exactMatch = true) 896 { 897 foreach (SourceTree source in forest) 898 { 899 if (data == source.Data && exactMatch) 900 return source; 901 SourceTree child = GetSourceTree(data, source.Children, exactMatch); 902 if (child != null) return child; 903 else if (!exactMatch && data.StartsWith(source.Data)) 904 return source; 905 } 906 907 return null; 908 } 909 910 /// <summary> 911 /// Gets a specific source given its data 912 /// </summary> 913 /// <param name="data">The data of the source</param> 914 /// <returns>The source with the corresponding data if one could be found, otherwise null</returns> 915 private static SourceData GetSourceData(String data) 916 { 917 foreach (SourceData s in SettingsManager.Sources) 918 if (s.Data == data) 919 return s; 920 921 return null; 922 } 923 924 /// <summary> 925 /// Gets a specific janitor 926 /// </summary> 927 /// <param name="drive">The drive that the janitor is responsible of</param> 928 /// <returns>The janitor if one could be found, otherwise null</returns> 929 private static FileSystemWatcher GetJanitor(String drive) 930 { 931 foreach (FileSystemWatcher fsw in janitors) 932 if (fsw.Path == drive) 933 return fsw; 934 return null; 935 } 936 937 /// <summary> 938 /// Creates a janitor for a specific drive 939 /// </summary> 940 /// <param name="drive">The drive that the janitor is responsible of</param> 941 private static void SetupJanitor(String drive) 942 { 943 if (GetJanitor(drive) != null) return; 944 945 FileSystemWatcher fsw = new FileSystemWatcher(); 946 947 if (!Directory.Exists(drive)) return; 948 949 U.L(LogLevel.Debug, "FILESYSTEM", "Started watching for events on " + drive); 950 fsw.Path = drive; 951 fsw.IncludeSubdirectories = true; 952 953 fsw.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName | 954 NotifyFilters.LastWrite | NotifyFilters.Security | NotifyFilters.Size; 955 fsw.Changed += Janitor_FileChanged; 956 fsw.Created += Janitor_FileChanged; 957 fsw.Deleted += Janitor_FileChanged; 958 fsw.Renamed += Janitor_FileRenamed; 959 fsw.EnableRaisingEvents = true; 960 janitors.Add(fsw); 961 } 962 963 /// <summary> 964 /// Removes a specific janitor 965 /// </summary> 966 /// <param name="drive">The drive that the janitor is responsible of</param> 967 private static void RemoveJanitor(String drive) 968 { 969 FileSystemWatcher fsw = GetJanitor(drive); 970 if (fsw == null) return; 971 972 U.L(LogLevel.Debug, "FILESYSTEM", "Stopped watching for events on " + drive); 973 fsw.EnableRaisingEvents = false; 974 janitors.Remove(fsw); 975 } 976 977 /// <summary> 978 /// Get a list of folders inside a Windows 7 Library 979 /// </summary> 980 /// <param name="name">The name of the library</param> 981 /// <returns>A list of all folders inside <paramref name="name"/></returns> 982 private static List<String> GetLibraryFolders(string name) 983 { 984 List<string> ret = new List<string>(); 985 if (System.IO.File.Exists(librariesPath + name + ".library-ms")) 986 { 987 ShellLibrary lib = ShellLibrary.Load(name, librariesPath, true); 988 foreach (ShellFileSystemFolder folder in lib) 989 ret.Add(folder.Path); 990 lib.Close(); 991 } 992 return ret; 993 } 994 995 /// <summary> 996 /// Creates the forest <see cref="sourceForest"/> using 997 /// the sources in PropertiesWindow. 998 /// </summary> 999 private static List<SourceTree> GetSourceForest() 1000 { 1001 List<SourceTree> forest = new List<SourceTree>(); 1002 foreach (SourceData source in SettingsManager.Sources) 1003 { 1004 if (source.Type == SourceType.Library) 1005 foreach (String folder in GetLibraryFolders(source.Data)) 1006 InsertSourceTree(new SourceTree() { Data = folder, Ignore = source.Ignore, Children = new List<SourceTree>() }, forest); 1007 else 1008 InsertSourceTree(new SourceTree() { Data = source.Data, Ignore = source.Ignore, Children = new List<SourceTree>() }, forest); 1009 } 1010 return forest; 1011 } 1012 1013 /// <summary> 1014 /// Insert a source tree into a forest. The tree will be inserted into the forest so that 1015 /// the parent will be a folder which contains the folder that the tree describes. 1016 /// </summary> 1017 /// <param name="tree">The tree to insert</param> 1018 /// <param name="forest">The forest to insert the tree into</param> 1019 private static void InsertSourceTree(SourceTree tree, List<SourceTree> forest) 1020 { 1021 for (int i=0; i<forest.Count; i++) 1022 { 1023 SourceTree t = forest[i]; 1024 1025 if (tree.Data == t.Data) 1026 return; 1027 1028 // insert tree into t 1029 else if (tree.Data.StartsWith(t.Data)) 1030 { 1031 InsertSourceTree(tree, t.Children); 1032 return; 1033 } 1034 1035 // swap t for tree and make t a child of tree 1036 else if (t.Data.StartsWith(tree.Data)) 1037 { 1038 tree.Children.Add(t); 1039 forest.RemoveAt(i); 1040 forest.Insert(i, tree); 1041 1042 // continue through the forest and find any additional children for tree 1043 for (int j = i + 1; j < forest.Count; j++) 1044 { 1045 t = forest[j]; 1046 if (t.Data.StartsWith(tree.Data)) 1047 { 1048 tree.Children.Add(t); 1049 forest.RemoveAt(j--); 1050 } 1051 } 1052 return; 1053 } 1054 } 1055 // insert tree into forest as top tree 1056 forest.Add(tree); 1057 } 1058 1059 /// <summary> 1060 /// Remove a source tree from a forest 1061 /// </summary> 1062 /// <param name="tree">The data of the tree to be removed</param> 1063 /// <param name="forest">The forest to remove the tree from</param> 1064 private static void RemoveSourceTree(String tree, List<SourceTree> forest) 1065 { 1066 // iterate through the forest and find the tree's position 1067 for (int i=0; i < forest.Count; i++) 1068 { 1069 SourceTree t = forest[i]; 1070 1071 // found the tree inside the forest 1072 if (t.Data == tree) 1073 { 1074 // put all children into the parent 1075 foreach (SourceTree child in t.Children) 1076 forest.Add(child); 1077 1078 forest.RemoveAt(i); 1079 return; 1080 } 1081 1082 // found a parent of the tree 1083 else if (t.Data.StartsWith(tree)) 1084 { 1085 RemoveSourceTree(tree, t.Children); 1086 return; 1087 } 1088 } 1089 } 1090 1091 /// <summary> 1092 /// Scans for Windows 7 Libraries of type Music and adds them to the collection. 1093 /// This is called from ScanLibraries() in either a new thread or in callers thread. 1094 /// </summary> 1095 /// <param name="ScanSourcesWhenDone">Set to true to scan the folders afterwards</param> 1096 private static void ScanLibrariesFunc(bool ScanSourcesWhenDone = false) 1097 { 1098 String librariesPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) 1099 + @"\AppData\Roaming\Microsoft\Windows\Libraries\"; 1100 1101 // remove non-existing libraries from sources 1102 for (int i = 0; i < SettingsManager.Sources.Count; i++) 1103 if (SettingsManager.Sources[i].Type == SourceType.Library && 1104 !System.IO.File.Exists(librariesPath + SettingsManager.Sources[i].Data + ".library-ms")) 1105 SettingsManager.Sources.RemoveAt(i--); 1106 1107 // add newly created libraries 1108 DirectoryInfo librariesInfo = new DirectoryInfo(librariesPath); 1109 FileInfo[] libraries = librariesInfo.GetFiles("*.library-ms"); 1110 foreach (FileInfo libraryInfo in libraries) 1111 { 1112 String libraryName = System.IO.Path.GetFileNameWithoutExtension(libraryInfo.FullName); 1113 ShellLibrary library = ShellLibrary.Load(libraryName, librariesPath, true); 1114 1115 // newly created libraries will not get a LibraryType so we get an exception here 1116 try 1117 { 1118 // make sure that the library is added 1119 if (library.LibraryType == LibraryFolderType.Music) 1120 { 1121 bool AlreadyAdded = false; 1122 foreach (SourceData src in SettingsManager.Sources) 1123 { 1124 if (src.Data == libraryName && src.Type == SourceType.Library) 1125 { 1126 AlreadyAdded = true; 1127 break; 1128 } 1129 } 1130 if (!AlreadyAdded) 1131 { 1132 SourceData s = new SourceData(); 1133 s.Data = libraryName; 1134 s.HumanType = U.T("SourcesTypeLibrary"); 1135 s.Type = SourceType.Library; 1136 s.Include = true; 1137 s.Icon = "pack://application:,,,/Platform/Windows/GUI/Images/Icons/Library.ico"; 1138 AddSource(s); 1139 } 1140 } 1141 else // remove the library if neccessary 1142 { 1143 for (int i = 0; i < SettingsManager.Sources.Count; i++) 1144 if (SettingsManager.Sources[i].Data == libraryName && SettingsManager.Sources[i].Type == SourceType.Library) 1145 SettingsManager.Sources.RemoveAt(i--); 1146 } 1147 } 1148 catch 1149 { 1150 // the library was (probably) newly created and didn't get a LibraryType, so we ignore it 1151 } 1152 } 1153 if (ScanSourcesWhenDone) ScanSources(); 1154 } 1155 1156 /// <summary> 1157 /// Calculates the difference between two source forests 1158 /// </summary> 1159 /// <param name="forest1">The original forest</param> 1160 /// <param name="forest2">The updated forest</param> 1161 /// <param name="added">A list which will after calculation contain all trees in forest2 and not forest1</param> 1162 /// <param name="removed">A list which will after calculation contain all trees in forest1 and not forest2</param> 1163 private static void GetForestDifference(List<SourceTree> forest1, List<SourceTree> forest2, List<SourceTree> added, List<SourceTree> removed) 1164 { 1165 // find all trees that are in forest1 but not forest2 1166 foreach (SourceTree tree1 in forest1) 1167 { 1168 bool isRemoved = true; 1169 foreach (SourceTree tree2 in forest2) 1170 { 1171 if (tree2.Data == tree1.Data) 1172 { 1173 GetForestDifference(tree1.Children, tree2.Children, added, removed); 1174 isRemoved = false; 1175 1176 if (tree2.Ignore && tree1.Include && !isRemoved) 1177 added.Add(tree1); 1178 else if (tree2.Include && tree1.Ignore && !isRemoved) 1179 removed.Add(tree1); 1180 1181 break; 1182 } 1183 } 1184 if (isRemoved) 1185 removed.Add(tree1); 1186 } 1187 1188 // find all trees that are in forest2 but not forest1 1189 foreach (SourceTree tree2 in forest2) 1190 { 1191 bool isAdded = true; 1192 foreach (SourceTree tree1 in forest1) 1193 { 1194 if (tree2.Data == tree1.Data) 1195 { 1196 isAdded = false; 1197 break; 1198 } 1199 } 1200 if (isAdded) 1201 added.Add(tree2); 1202 } 1203 } 1204 1205 #endregion 1206 1207 #region Event handlers 1208 1209 /// <summary> 1210 /// Invoked when a property of the settings manager changes 1211 /// </summary> 1212 /// <param name="sender">The sender of the event</param> 1213 /// <param name="e">The event data</param> 1214 private static void SettingsManager_PropertyChanged(object sender, PropertyChangedWithValuesEventArgs e) 1215 { 1216 switch (e.PropertyName) 1217 { 1218 case "Sources": 1219 ScanSources(); 1220 break; 1221 } 1222 } 1223 1224 /// <summary> 1225 /// Event handler that gets called when a Library has been renamed 1226 /// </summary> 1227 /// <param name="sender">The sender of the event</param> 1228 /// <param name="e">The event data</param> 1229 private static void Janitor_LibraryRenamed(object sender, RenamedEventArgs e) 1230 { 1231 string oldName = System.IO.Path.GetFileNameWithoutExtension(e.OldName); 1232 string newName = System.IO.Path.GetFileNameWithoutExtension(e.Name); 1233 1234 foreach (JanitorTask jt in janitorTasks) 1235 { 1236 // on delete this event will fire afterwards, but we should skip it 1237 if (jt.Job == JanitorJob.DeleteLibrary && jt.Data == newName) 1238 return; 1239 1240 // check if this job is already in the queue 1241 if (jt.Job == JanitorJob.RenameLibrary && jt.Data == oldName + "\n" + newName) 1242 return; 1243 } 1244 1245 if (janitorLazyness != null) 1246 janitorLazyness.Dispose(); 1247 janitorTasks.Add(new JanitorTask() { Data = oldName + "\n" + newName, Job = JanitorJob.RenameLibrary }); 1248 janitorLazyness = new Timer(Janitor_Clean, null, 400, 3600); 1249 } 1250 1251 /// <summary> 1252 /// Event handler that gets called when a Library has been modified 1253 /// </summary> 1254 /// <param name="sender">The sender of the event</param> 1255 /// <param name="e">The event data</param> 1256 private static void Janitor_LibraryChanged(object sender, FileSystemEventArgs e) 1257 { 1258 JanitorTask task = new JanitorTask() { Data = System.IO.Path.GetFileNameWithoutExtension(e.Name) }; 1259 if (e.ChangeType == WatcherChangeTypes.Deleted) 1260 task.Job = JanitorJob.DeleteLibrary; 1261 else if (e.ChangeType == WatcherChangeTypes.Created) 1262 task.Job = JanitorJob.CreateLibrary; 1263 else if (e.ChangeType == WatcherChangeTypes.Changed) 1264 task.Job = JanitorJob.UpdateLibrary; 1265 else 1266 return; 1267 1268 foreach (JanitorTask jt in janitorTasks) 1269 { 1270 // on rename and create this event will fire afterwards, but we should skip it 1271 if (e.ChangeType == WatcherChangeTypes.Changed && 1272 ((jt.Job == JanitorJob.RenameLibrary && jt.Data.EndsWith("\n" + System.IO.Path.GetFileNameWithoutExtension(e.Name))) || 1273 (jt.Job == JanitorJob.CreateLibrary && jt.Data == System.IO.Path.GetFileNameWithoutExtension(e.Name)))) 1274 { 1275 return; 1276 } 1277 1278 // check if this job is already in the queue 1279 if (jt.Job == task.Job && jt.Data == task.Data) 1280 { 1281 return; 1282 } 1283 1284 // an update may be preceded by a delete, so we change the delete task into an update task 1285 if (task.Job == JanitorJob.UpdateLibrary && jt.Job == JanitorJob.DeleteLibrary && task.Data == jt.Data) 1286 { 1287 jt.Job = JanitorJob.UpdateLibrary; 1288 return; 1289 } 1290 1291 // an update may be followed by a delete, so we skip the delete 1292 if (task.Job == JanitorJob.DeleteLibrary && jt.Job == JanitorJob.UpdateLibrary && task.Data == jt.Data) 1293 { 1294 return; 1295 } 1296 } 1297 1298 if (janitorLazyness != null) 1299 janitorLazyness.Dispose(); 1300 janitorTasks.Add(task); 1301 janitorLazyness = new Timer(Janitor_Clean, null, 400, 3600); 1302 } 1303 1304 /// <summary> 1305 /// Event handler that gets called when a file has been renamed 1306 /// </summary> 1307 /// <param name="sender">The sender of the event</param> 1308 /// <param name="e">The event data</param> 1309 private static void Janitor_FileRenamed(object sender, RenamedEventArgs e) 1310 { 1311 bool isDir = Directory.Exists(e.FullPath); 1312 1313 if (!isDir && !MediaManager.IsSupported(e.FullPath)) 1314 return; 1315 1316 JanitorTask task = new JanitorTask() { Data = e.OldFullPath + "\n" + e.FullPath, Job = isDir ? JanitorJob.RenameFolder : JanitorJob.RenameFile }; 1317 1318 foreach (JanitorTask jt in janitorTasks) 1319 { 1320 // on delete this event will fire afterwards, but we should skip it 1321 if (jt.Job == JanitorJob.DeleteFile && jt.Data == e.FullPath) 1322 return; 1323 1324 // check if this job is already in the queue 1325 if (jt.Job == task.Job && jt.Data == task.Data) 1326 return; 1327 } 1328 //janitorLazyness.Stop(); 1329 if (janitorLazyness != null) 1330 janitorLazyness.Dispose(); 1331 janitorTasks.Add(task); 1332 janitorLazyness = new Timer(Janitor_Clean, null, 400, 3600); 1333 } 1334 1335 /// <summary> 1336 /// Event handler that gets called when a file has been modified 1337 /// </summary> 1338 /// <param name="sender">The sender of the event</param> 1339 /// <param name="e">The event data</param> 1340 private static void Janitor_FileChanged(object sender, FileSystemEventArgs e) 1341 { 1342 bool isDir = Directory.Exists(e.FullPath); 1343 1344 if (!PathIsAdded(e.FullPath) || !MediaManager.IsSupported(e.FullPath)) 1345 return; 1346 1347 JanitorTask task = new JanitorTask() { Data = e.FullPath }; 1348 if (e.ChangeType == WatcherChangeTypes.Deleted) 1349 { 1350 if (MediaManager.IsSupported(e.FullPath)) 1351 task.Job = JanitorJob.DeleteFile; 1352 else if (Directory.Exists(e.FullPath)) 1353 task.Job = JanitorJob.DeleteFolder; 1354 else 1355 return; 1356 } 1357 else if (e.ChangeType == WatcherChangeTypes.Created) 1358 task.Job = isDir ? JanitorJob.CreateFolder : JanitorJob.CreateFile; 1359 1360 else if (e.ChangeType == WatcherChangeTypes.Changed) 1361 { 1362 if (isDir) return; 1363 task.Job = JanitorJob.UpdateFile; 1364 } 1365 else 1366 return; 1367 1368 foreach (JanitorTask jt in janitorTasks) 1369 { 1370 // on create folder this event will fire afterwards, but we should skip it 1371 if (!isDir && jt.Job == JanitorJob.CreateFolder && e.ChangeType == WatcherChangeTypes.Changed && jt.Data == e.FullPath) 1372 return; 1373 1374 // on rename and create this event will fire afterwards, but we should skip it 1375 if (e.ChangeType == WatcherChangeTypes.Changed && 1376 ((jt.Job == JanitorJob.RenameFile && jt.Data.EndsWith("\n" + e.FullPath)) || 1377 (jt.Job == JanitorJob.CreateFile && jt.Data == e.FullPath))) 1378 return; 1379 1380 // check if this job is already in the queue 1381 if (jt.Job == task.Job && jt.Data == task.Data) 1382 return; 1383 } 1384 1385 if (janitorLazyness != null) 1386 janitorLazyness.Dispose(); 1387 janitorTasks.Add(task); 1388 janitorLazyness = new Timer(Janitor_Clean, null, 400, 3600); 1389 } 1390 1391 /// <summary> 1392 /// Performs the actual cleaning of the janitor. 1393 /// This is called from a timer because when a file is modified several events will fire but 1394 /// we only need to act once. 1395 /// 1396 /// This method will take action depending on what have happened, keeping the collection in 1397 /// sync with the filesystem. 1398 /// </summary> 1399 /// <param name="state">The timer state</param> 1400 private static void Janitor_Clean(object state) 1401 { 1402 janitorLazyness.Dispose(); 1403 janitorLazyness = null; 1404 1405 // copy tasks and clear the list in case another event is fired while we are working 1406 List<JanitorTask> tasks = new List<JanitorTask>(janitorTasks); 1407 janitorTasks.Clear(); 1408 1409 foreach (JanitorTask task in tasks) 1410 { 1411 SourceData s = null; 1412 switch (task.Job) 1413 { 1414 case JanitorJob.CreateFile: 1415 AddFile(task.Data, true); 1416 break; 1417 1418 case JanitorJob.DeleteFile: 1419 RemoveFile(task.Data); 1420 break; 1421 1422 case JanitorJob.CreateLibrary: 1423 try 1424 { 1425 ShellLibrary library = ShellLibrary.Load(task.Data, librariesPath, true); 1426 s = GetSourceData(task.Data); 1427 if (library.LibraryType == LibraryFolderType.Music && s == null) 1428 AddSource(new SourceData 1429 { 1430 Data = task.Data, 1431 Include = true, 1432 HumanType = U.T("SourcesTypeLibrary"), 1433 Type = SourceType.Library 1434 }); 1435 } 1436 catch (Exception exc) 1437 { 1438 U.L(LogLevel.Warning, "FILESYSTEM", "Could not read newly created Library " + task.Data + ": " + exc.Message); 1439 } 1440 break; 1441 1442 case JanitorJob.CreateFolder: 1443 case JanitorJob.DeleteFolder: 1444 ScanSources(); 1445 break; 1446 1447 case JanitorJob.DeleteLibrary: 1448 s = GetSourceData(task.Data); 1449 if (s != null) 1450 RemoveSource(s); 1451 break; 1452 1453 case JanitorJob.RenameFolder: 1454 case JanitorJob.RenameFile: 1455 string[] names = task.Data.Split('\n'); 1456 RenamePath(names[0], names[1], SettingsManager.FileTracks); 1457 RenamePath(names[0], names[1], SettingsManager.QueueTracks); 1458 RenamePath(names[0], names[1], SettingsManager.HistoryTracks); 1459 foreach (PlaylistData p in SettingsManager.Playlists) 1460 RenamePath(names[0], names[1], p.Tracks); 1461 break; 1462 1463 case JanitorJob.RenameLibrary: 1464 string[] lnames = task.Data.Split('\n'); 1465 String oldLibraryName = System.IO.Path.GetFileNameWithoutExtension(lnames[0]); 1466 String newLibraryName = System.IO.Path.GetFileNameWithoutExtension(lnames[1]); 1467 s = GetSourceData(oldLibraryName); 1468 if (s != null) 1469 s.Data = newLibraryName; 1470 break; 1471 1472 case JanitorJob.UpdateFile: 1473 DispatchPathModified(task.Data); 1474 break; 1475 1476 case JanitorJob.UpdateLibrary: 1477…
Large files files are truncated, but you can click here to view the full file