PageRenderTime 108ms CodeModel.GetById 19ms app.highlight 77ms RepoModel.GetById 1ms app.codeStats 0ms

/Application/Core/FilesystemManager.cs

http://yet-another-music-application.googlecode.com/
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