context: // PlacesItemSource.cs -- Modification of BookmarkItemSource
  //
  // GNOME Do is the legal property of its developers. Please refer to the
  // COPYRIGHT file distributed with this source distribution.
  //
  // This program is free software: you can redistribute it and/or modify
  // it under the terms of the GNU General Public License as published by
  // the Free Software Foundation, either version 3 of the License, or
  // (at your option) any later version.
  //
  // This program is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  // GNU General Public License for more details.
  //
  // You should have received a copy of the GNU General Public License
  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
  //
  
  using System;
  using System.IO;
  using System.Data;
  using System.Linq;
  using System.Collections.Generic;
  
  using Do.Platform;
  using Do.Platform.Common;
  using Do.Universe;
  using Do.Universe.Common;
  
  using Mono.Addins;
  using Mono.Data.Sqlite;
  	
  namespace Firefox
  {
  	public class PlacesItemSource : ItemSource
  	{
  		const string BeginProfileName = "Path=";
  		const string BeginDefaultProfile = "Default=1";
  		
  		// Collections for Storing Items to be used by Do
  		List<Item> items;
  		IEnumerable<PlaceItem> places;
  		IEnumerable<FolderItem> folders;
  		
  		string stored_temp_db_path;
  		
  		public PlacesItemSource ()
  		{
  			items = new List<Item> ();
  			places = Enumerable.Empty<PlaceItem> ();			
  			folders = Enumerable.Empty<FolderItem> ();
  
  			ProfilePath = FindProfilePath ();
  		}
  
  		~PlacesItemSource ()
  		{
  			try {
  				if (File.Exists (stored_temp_db_path))
  					File.Delete (stored_temp_db_path);
  			} catch (IOException e) {
  				Log<PlacesItemSource>.Error ("Could not delete stored db file: {0}", e.Message);
  				Log<PlacesItemSource>.Debug (e.StackTrace);
  			}
  		}
  
  		public override string Name {
  			get { return AddinManager.CurrentLocalizer.GetString ("Firefox Places"); }
  		}
  
  		public override string Description {
  			get { return AddinManager.CurrentLocalizer.GetString ("Search your bookmarks and history."); }
  		}
  
  		public override string Icon {
  			get { return FirefoxHelpers.IconName; }
  		}
  
  		public override IEnumerable<Item> Items {
  			get { return items; }
  		}
  
  		public override IEnumerable<Type> SupportedItemTypes 
  		{
  			get {
  				yield return typeof (PlaceItem); 
  				yield return typeof (FolderItem);
  				yield return typeof (IApplicationItem);				
  				yield return typeof (BrowseHistoryItem);				
  				yield return typeof (BrowseBookmarkItem);				
  			}
  		}
  
  		public override IEnumerable<Item> ChildrenOfItem (Item item) 
  		{
  			if (IsFirefox (item)) {
  				yield return new BrowseHistoryItem ();
  				yield return new BrowseBookmarkItem ();
  			} else if (item is BrowseBookmarkItem || item is BrowseHistoryItem) {
  				foreach (FolderItem folder in folders)
  					yield return folder;
  				
  				if (item is BrowseBookmarkItem)
  					foreach (PlaceItem place in PlacesWithParent)
  						yield return place;
  				else
  					foreach (PlaceItem place in places)
  						yield return place;
  			} else if (item is FolderItem) {
  				FolderItem parent = (FolderItem) item;
  				
  				foreach (FolderItem folder in folders.Where (folder => folder.ParentId == parent.Id))
  					yield return folder;
  				
  				foreach (PlaceItem place in PlacesWithParent.Where (place => place.ParentId == parent.Id))
  					yield return place;
  			}
  		}
  		
  		public override void UpdateItems ()
  		{
  			// evaluate immediate to allow sql connections to close
  			places = LoadPlaceItems ().ToArray ();
  			folders = LoadFolderItems ().ToArray ();
  			
  			items.Clear();
  
  			items.Add (new BrowseHistoryItem ());
  			items.Add (new BrowseBookmarkItem ());
  			
  			items.AddRange (folders.OfType<Item> ());
  			items.AddRange (PlacesWithParent.OfType<Item> ());
  		}
  
  		IEnumerable<PlaceItem> PlacesWithParent {
  			get { return places.Where (place => place.ParentId.HasValue); }
  		}
  		
  		bool IsFirefox (Item item) 
  		{
  			return item.Equals (Do.Platform.Services.UniverseFactory.MaybeApplicationItemFromCommand ("firefox"));
  		}
  		
  		/// <summary>
  		/// Looks in the firefox profiles file (~/.mozilla/firefox/profiles.ini)
  		/// for the name of the default profile, and returns the path to the
  		/// default profile.
  		/// </summary>
  		/// <returns>
  		/// A <see cref="System.String"/> containing the absolute path to the
  		/// bookmarks.html file of the default firefox profile for the current
  		/// user.
  		/// </returns>
  		string ProfilePath { get; set; }
  
  		string FindProfilePath ()
  		{
  			string line, profile, path, home;
  
  			profile = null;
  			home = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
  			
  			path = Path.Combine (home, ".mozilla/firefox/profiles.ini");
  			using (StreamReader r = File.OpenText (path)) {
  				while ((line = r.ReadLine ()) != null) {
  					if (line.StartsWith (BeginDefaultProfile)) {
  						break;
  					} else if (line.StartsWith (BeginProfileName)) {
  						line = line.Trim ();
  						line = line.Substring (BeginProfileName.Length);
  						profile = line;
  					}
  				}
  			}
  			return new [] {home, ".mozilla", "firefox", profile}.Aggregate (Path.Combine); 
  		}
  
  
  		string FirefoxDBPath {
  			get {
  				return Path.Combine (ProfilePath, "places.sqlite");
  			}
  		}
  		
  		/// <summary>
  		/// Looks at the file currently saved in the temp folder and sees if it
  		/// needs to be updated.
  		/// </summary>
  		/// <returns>
  		/// The path of the current database file in memory.
  		/// </returns>
  		string TempDatabasePath {
  			get {				
  				// Check if the stored temp file exists and if it doesn't, make one.
  				if  (string.IsNullOrEmpty (stored_temp_db_path) || !File.Exists (stored_temp_db_path)) {
  					stored_temp_db_path = Path.GetTempFileName ();
  					System.IO.File.Copy (FirefoxDBPath, stored_temp_db_path, true);
  				} else if (File.Exists (stored_temp_db_path)) {
  					FileInfo firefoxDBFileInfo = new FileInfo (FirefoxDBPath);
  					FileInfo tempDBFileInfo = new FileInfo (stored_temp_db_path);
  					
  					if (firefoxDBFileInfo.LastWriteTimeUtc > tempDBFileInfo.LastWriteTimeUtc)
  						System.IO.File.Copy (FirefoxDBPath, stored_temp_db_path, true);
  				}
  				return stored_temp_db_path;
  			}
  		}
  		
  		/// <summary>
  		/// Creates the current SQL connection string to the temporary database in use. 
  		/// </summary>
  		/// <returns>
  		/// The current SQL connection string to the temporary database in use.
  		/// </returns>
  		string ConnectionString {
  			get { return String.Format ("URI=file:{0},version=3", TempDatabasePath); }
  		}
  
  		/// <summary>
  		/// Opens a connection to the current temporary database copy, 
  		/// and searches for bookmark directories, then adds those to 
  		/// the IEnumerable collection to be returned. 
  		/// </summary>
  		/// <returns>
  		/// A collection of bookmark directory Folder Items.
  		/// </returns>
  		IEnumerable<FolderItem> LoadFolderItems ()
  		{
  			using (IDbConnection dbcon = (IDbConnection) new SqliteConnection (ConnectionString)) {
  				dbcon.Open ();
  
  				using (IDbCommand dbcmd = dbcon.CreateCommand ())
  				{
  					dbcmd.CommandText = "SELECT title, id, parent "
  						+ "FROM moz_bookmarks "
  						+ "WHERE type = 2";
  						
  					using (IDataReader reader = dbcmd.ExecuteReader ()) 
  					{
  						while (reader.Read ()) {
  							string title = reader.GetString (0);							
  							int id = reader.GetInt32 (1);
  
gt: 							int parent = reader.GetInt32 (2);