Index: src/Extensions/Banshee.LibraryWatcher/Banshee.LibraryWatcher.addin.xml
===================================================================
--- src/Extensions/Banshee.LibraryWatcher/Banshee.LibraryWatcher.addin.xml	(revision 0)
+++ src/Extensions/Banshee.LibraryWatcher/Banshee.LibraryWatcher.addin.xml	(revision 0)
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Addin 
+    id="Banshee.LibraryWatcher"
+    version="1.0"
+    compatVersion="1.0"
+    copyright="© 2008 Alexander Hixon. Licensed under the MIT X11 license."
+    name="Library Watcher"
+    category="Library"
+    description="Watches your library directory and imports and removes items from Banshee automatically."
+    author="Alexander Hixon"
+    url="http://banshee-project.org/"
+    defaultEnabled="true">
+
+  <Dependencies>
+    <Addin id="Banshee.Services" version="1.0"/>
+  </Dependencies>
+  
+  <Extension path="/Banshee/ServiceManager/Service">
+    <Service class="Banshee.LibraryWatcher.LibraryWatcherService"/>
+  </Extension>
+
+</Addin>
Index: src/Extensions/Banshee.LibraryWatcher/Banshee.LibraryWatcher.mdp
===================================================================
--- src/Extensions/Banshee.LibraryWatcher/Banshee.LibraryWatcher.mdp	(revision 0)
+++ src/Extensions/Banshee.LibraryWatcher/Banshee.LibraryWatcher.mdp	(revision 0)
@@ -0,0 +1,30 @@
+<Project name="Banshee.LibraryWatcher" fileversion="2.0" language="C#" clr-version="Net_2_0" UseParentDirectoryAsNamespace="True" ctype="DotNetProject">
+  <Configurations active="Debug">
+    <Configuration name="Debug" ctype="DotNetProjectConfiguration">
+      <Output directory="../../../bin" assemblyKeyFile="." assembly="Banshee.LibraryWatcher" />
+      <Build debugmode="True" target="Library" />
+      <Execution runwithwarnings="True" consolepause="True" runtime="MsNet" clr-version="Net_2_0" />
+      <CodeGeneration compiler="Mcs" warninglevel="4" optimize="True" unsafecodeallowed="False" generateoverflowchecks="True" generatexmldocumentation="False" ctype="CSharpCompilerParameters" />
+    </Configuration>
+  </Configurations>
+  <Contents>
+    <File name="Banshee.LibraryWatcher.addin.xml" subtype="Code" buildaction="EmbedAsResource" />
+    <File name="Banshee.LibraryWatcher/LibraryWatcherService.cs" subtype="Code" buildaction="Compile" />
+  </Contents>
+  <References>
+    <ProjectReference type="Project" localcopy="True" refto="Banshee.Core" />
+    <ProjectReference type="Project" localcopy="True" refto="Banshee.Services" />
+    <ProjectReference type="Project" localcopy="True" refto="Hyena" />
+    <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+    <ProjectReference type="Gac" localcopy="True" refto="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+  </References>
+  <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="./Makefile.am">
+    <BuildFilesVar Sync="True" Name="SOURCES" />
+    <DeployFilesVar />
+    <ResourcesVar Sync="True" Name="RESOURCES" />
+    <OthersVar />
+    <GacRefVar />
+    <AsmRefVar />
+    <ProjectRefVar />
+  </MonoDevelop.Autotools.MakefileInfo>
+</Project>
Index: src/Extensions/Banshee.LibraryWatcher/Banshee.LibraryWatcher/LibraryWatcherService.cs
===================================================================
--- src/Extensions/Banshee.LibraryWatcher/Banshee.LibraryWatcher/LibraryWatcherService.cs	(revision 0)
+++ src/Extensions/Banshee.LibraryWatcher/Banshee.LibraryWatcher/LibraryWatcherService.cs	(revision 0)
@@ -0,0 +1,222 @@
+// 
+// LibraryWatcherService.cs
+//
+// Author:
+//   Alexander Hixon <hixon.alexander@mediati.org>
+//
+// Copyright (C) 2008 Alexander Hixon
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Banshee.Base;
+using Banshee.Preferences;
+using Banshee.ServiceStack;
+using Banshee.Library;
+using Banshee.Collection.Database;
+using Banshee.Sources;
+
+using Hyena;
+
+namespace Banshee.LibraryWatcher
+{
+    public class LibraryWatcherService : IExtensionService, IDisposable
+    {
+        private FileSystemWatcher watcher;
+        private LibraryImportManager import;
+        private SourceManager source_manager;
+        
+        private List <string> import_list = new List <string> ();
+        private uint update_timeout_id;
+        
+        private int [] library_ids;
+        
+        public LibraryWatcherService ()
+        {
+        }
+        
+        void IExtensionService.Initialize ()
+        {
+            try {
+                import = ServiceManager.Get <LibraryImportManager> ();
+                source_manager = ServiceManager.Get <SourceManager> ();
+                
+                if (!ServiceStartup ()) {
+                    ServiceManager.ServiceStarted += OnServiceStarted;
+                }
+                } catch (Exception ex) {
+                Console.WriteLine ("!!!: {0}", ex);
+            }
+        }
+        
+        private void OnServiceStarted (ServiceStartedArgs args) 
+        {
+            if (args.Service is LibraryImportManager) {
+                import = (LibraryImportManager) args.Service;
+            } else if (args.Service is SourceManager) {
+                source_manager = (SourceManager) args.Service;
+            }
+                    
+            ServiceStartup ();
+        }
+        
+        private bool ServiceStartup ()
+        {
+            if (import == null || source_manager == null) {
+                return false;
+            }
+            
+            source_manager.SourceAdded += SourceAdded;
+            return true;
+        }
+        
+        private int music_dbid = 0;
+        private int video_dbid = 0;
+        private void SourceAdded (SourceAddedArgs args)
+        {
+            if (args.Source is MusicLibrarySource) {
+                MusicLibrarySource src = args.Source as MusicLibrarySource;
+                music_dbid = src.DbId;
+            } else if (args.Source is VideoLibrarySource) {
+                VideoLibrarySource src = args.Source as VideoLibrarySource;
+                video_dbid = src.DbId;
+            }
+            
+            if (music_dbid != 0 && video_dbid != 0 && !started_watcher) {
+                // Start watcher when both Music and Video libraries are added
+                started_watcher = true;
+                StartWatcher ();
+            }
+        }
+        
+        private bool started_watcher = false;
+        private void StartWatcher ()
+        {
+            library_ids = new int [] { music_dbid, video_dbid };
+            watcher = new FileSystemWatcher ();
+            
+            // TODO: Subscribe to library location change event, and change watcher path
+            watcher.Path = Paths.CachedLibraryLocation;
+            Log.DebugFormat ("Watching in: {0}", Paths.CachedLibraryLocation);
+            
+            watcher.Created += new FileSystemEventHandler (FileSystemEvent);
+            watcher.Deleted += new FileSystemEventHandler (FileSystemEvent);
+            watcher.Changed += new FileSystemEventHandler (FileSystemEvent);
+            watcher.Renamed += new RenamedEventHandler (RenamedEvent);
+            watcher.IncludeSubdirectories = true;
+            
+            // Start watching
+            watcher.EnableRaisingEvents = true;
+        }
+        
+        public void Dispose ()
+        {
+            watcher.EnableRaisingEvents = false;
+        }
+        
+        private void FileSystemEvent (object source, FileSystemEventArgs e)
+        {
+            Log.DebugFormat ("FileSystemEvent: type={0}, fullpath={1}, name={2}", e.ChangeType, e.FullPath, e.Name);
+            
+            if (e.ChangeType == WatcherChangeTypes.Created) {
+                // Created is called when either:
+                //  - a new file is created inside the watched directory; OR
+                //  - a file is moved from an outside directory inside the watched directory.
+                //    Note: the Renamed event is only fired for files inside the watch.
+                
+                import_list.Add (e.FullPath);
+            } else if (e.ChangeType == WatcherChangeTypes.Deleted) {
+                string relative_path = e.FullPath.Substring (Paths.CachedLibraryLocation.Length + 1);
+                int track_id = DatabaseTrackInfo.GetTrackIdForUri (relative_path, library_ids);
+                
+                if (track_id > 0) {
+                    DatabaseTrackInfo track = DatabaseTrackInfo.FindFromID (track_id);
+                    track.Remove (true);
+                    Console.WriteLine ("Removed from table!");
+                }
+            } else if (e.ChangeType == WatcherChangeTypes.Changed) {
+                // First Changed event is OpenWrite, last is CloseWrite. There may
+                // be zero or more events in between these two, especially when copying.
+                
+                // Wait for one second of no activity before assuming files were
+                // copied OK and import them (useless importing an empty file)
+                if (update_timeout_id > 0) {
+                    Console.WriteLine ("'QueueImport' timeout reset.");
+                    GLib.Source.Remove (update_timeout_id);
+                    update_timeout_id = 0;
+                }
+                
+                GLib.Timeout.Add (1200, new GLib.TimeoutHandler (QueueImport));
+            }
+        }
+        
+        private void RenamedEvent (object source, RenamedEventArgs e)
+        {
+            if (File.Exists (e.FullPath)) {
+                RenameTrackPath (e.OldFullPath, e.FullPath);
+            } else {
+                // Entire directory rename - recursively scan tree and update children
+                ScanPathForRenames (e.FullPath, e.OldFullPath, e.FullPath);
+            }
+        }
+        
+        private void ScanPathForRenames (string path, string oldname, string newname)
+        {
+            foreach (string directory in Directory.GetDirectories (path)) {
+                ScanPathForRenames (directory, oldname, newname);
+            }
+            
+            foreach (string file in Directory.GetFiles (path)) {
+                RenameTrackPath (Path.Combine (oldname, file.Substring (newname.Length + 1)), file);
+            }
+        }
+        
+        private bool QueueImport ()
+        {
+            Log.DebugFormat ("Importing {0} tracks.", import_list.Count);
+            import.QueueSource (import_list.ToArray ());
+            import_list.Clear ();
+            
+            return false;
+        }
+        
+        private void RenameTrackPath (string oldpath, string newpath)
+        {
+            Log.DebugFormat ("{0} => {1}", oldpath, newpath);
+            
+            string old_relative_path = oldpath.Substring (Paths.CachedLibraryLocation.Length + 1);
+            int track_id = DatabaseTrackInfo.GetTrackIdForUri (old_relative_path, library_ids);
+            
+            if (track_id > 0) {
+                DatabaseTrackInfo track = DatabaseTrackInfo.FindFromID (track_id);
+                track.Uri = new SafeUri (newpath);
+                track.Save (true);
+            }
+        }
+        
+        string IService.ServiceName {
+            get { return "LibraryWatcher"; }
+        }
+    }
+}
Index: src/Extensions/Banshee.LibraryWatcher/Makefile.am
===================================================================
--- src/Extensions/Banshee.LibraryWatcher/Makefile.am	(revision 0)
+++ src/Extensions/Banshee.LibraryWatcher/Makefile.am	(revision 0)
@@ -0,0 +1,10 @@
+ASSEMBLY = Banshee.LibraryWatcher
+TARGET = library
+LINK = $(REF_EXTENSION_LIBRARYWATCHER)
+
+SOURCES = Banshee.LibraryWatcher/LibraryWatcherService.cs
+
+RESOURCES = Banshee.LibraryWatcher.addin.xml
+
+include $(top_srcdir)/build/build.mk
+
Index: src/Extensions/Makefile.am
===================================================================
--- src/Extensions/Makefile.am	(revision 4168)
+++ src/Extensions/Makefile.am	(working copy)
@@ -7,6 +7,7 @@
 	Banshee.FileSystemQueue \
 	Banshee.InternetRadio \
 	Banshee.Lastfm \
+	Banshee.LibraryWatcher \
 	Banshee.MiniMode \
 	Banshee.MultimediaKeys \
 	Banshee.NotificationArea \
Index: src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs
===================================================================
--- src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs	(revision 4168)
+++ src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs	(working copy)
@@ -79,6 +79,25 @@
         {
             Provider.Copy (original, this);
         }
+        
+        private static HyenaSqliteCommand select_command = new HyenaSqliteCommand (String.Format (
+            "SELECT {0} FROM {1} WHERE {2} AND CoreTracks.TrackID = ?",
+            provider.Select, provider.From,
+            (String.IsNullOrEmpty (provider.Where) ? "1=1" : provider.Where)
+        ));
+        
+        public static DatabaseTrackInfo FindFromID (int track_id)
+        {
+            DatabaseTrackInfo track = null;
+            
+            using (IDataReader reader = ServiceManager.DbConnection.Query (select_command, track_id)) {
+                if (reader.Read ()) {
+                    track = provider.Load (reader);
+                }
+            }
+            
+            return track;
+        }
 
         public override void IncrementPlayCount ()
         {
@@ -518,6 +537,25 @@
                 }
             }
         }
+        
+        private HyenaSqliteCommand delete_command = new HyenaSqliteCommand (
+            "DELETE FROM CoreTracks WHERE TrackID = ?"
+        );
+        
+        /*private HyenaSqliteCommand no_reimport_command = new HyenaSqliteCommand (
+            "INSERT INTO CoreRemovedTracks VALUES (SELECT ?, TrackID, Uri FROM CoreTracks WHERE TrackID IN (SELECT ItemID FROM CoreCache WHERE ModelID = ?);"
+        );*/
+        
+        public void Remove (bool can_reimport)
+        {
+            if (!can_reimport) {
+                // TODO: Run no_reimport_command
+            }
+            
+            PrimarySource source = this.PrimarySource;
+            ServiceManager.DbConnection.Execute (delete_command, this.track_id);
+            source.NotifyTracksDeleted ();
+        }
 
         private static HyenaSqliteCommand check_command = new HyenaSqliteCommand (
             "SELECT TrackID FROM CoreTracks WHERE PrimarySourceId IN (?) AND (Uri = ? OR Uri = ?) LIMIT 1"
Index: build/build.environment.mk
===================================================================
--- build/build.environment.mk	(revision 4168)
+++ build/build.environment.mk	(working copy)
@@ -119,6 +119,7 @@
 REF_EXTENSION_NOTIFICATIONAREA = $(LINK_BANSHEE_THICKCLIENT_DEPS)
 REF_EXTENSION_PLAYQUEUE = $(LINK_BANSHEE_THICKCLIENT_DEPS)
 REF_EXTENSION_LASTFM = $(LINK_BANSHEE_THICKCLIENT_DEPS) $(LINK_MONO_MEDIA) $(LINK_LASTFM) $(LINK_LASTFM_GUI)
+REF_EXTENSION_LIBRARYWATCHER = $(LINK_BANSHEE_SERVICES_DEPS)
 REF_EXTENSION_NOWPLAYING = $(LINK_BANSHEE_THICKCLIENT_DEPS)
 REF_EXTENSION_PODCASTING = $(LINK_MIGO_DEPS) $(LINK_BANSHEE_THICKCLIENT_DEPS)
 REF_EXTENSION_SAMPLE = $(LINK_BANSHEE_THICKCLIENT_DEPS)
Index: configure.ac
===================================================================
--- configure.ac	(revision 4168)
+++ configure.ac	(working copy)
@@ -165,6 +165,7 @@
 src/Extensions/Banshee.FileSystemQueue/Makefile
 src/Extensions/Banshee.InternetRadio/Makefile
 src/Extensions/Banshee.Lastfm/Makefile
+src/Extensions/Banshee.LibraryWatcher/Makefile
 src/Extensions/Banshee.MiniMode/Makefile
 src/Extensions/Banshee.MultimediaKeys/Makefile
 src/Extensions/Banshee.NotificationArea/Makefile
