Thumbnail file hits. Based on a patch from D Bera
[beagle.git] / beagled / FileAttributesStore_Sqlite.cs
blob3489245ee7cfc8ea46b3781bb7ccbefa5b429a89
1 //
2 // FileAttributesStore_Sqlite.cs
3 //
4 // Copyright (C) 2004 Novell, Inc.
5 //
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to deal
10 // in the Software without restriction, including without limitation the rights
11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 // copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in all
16 // copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 // SOFTWARE.
27 using System;
28 using System.Collections;
29 using System.IO;
30 using System.Threading;
32 using Mono.Data.SqliteClient;
34 using Beagle.Util;
36 namespace Beagle.Daemon {
38 public class FileAttributesStore_Sqlite : IFileAttributesStore {
40 // Version history:
41 // 1: Original version
42 // 2: Replaced LastIndexedTime with LastAttrTime
43 const int VERSION = 2;
45 private SqliteConnection connection;
46 private BitArray path_flags;
47 private int transaction_count = 0;
49 enum TransactionState {
50 None,
51 Requested,
52 Started
54 private TransactionState transaction_state;
56 public FileAttributesStore_Sqlite (string directory, string index_fingerprint)
58 bool create_new_db = false;
59 path_flags = new BitArray (65536);
61 if (! File.Exists (GetDbPath (directory))) {
62 create_new_db = true;
63 } else {
64 connection = Open (directory);
66 SqliteCommand command;
67 SqliteDataReader reader = null;
68 int stored_version = 0;
69 string stored_fingerprint = null;
72 command = new SqliteCommand ();
73 command.Connection = connection;
74 command.CommandText =
75 "SELECT version, fingerprint FROM db_info";
76 try {
77 reader = ExecuteReaderOrWait (command);
78 } catch (Exception ex) {
79 create_new_db = true;
81 if (reader != null && ! create_new_db) {
82 if (ReadOrWait (reader)) {
83 stored_version = reader.GetInt32 (0);
84 stored_fingerprint = reader.GetString (1);
86 reader.Close ();
88 command.Dispose ();
90 if (VERSION != stored_version
91 || (index_fingerprint != null && index_fingerprint != stored_fingerprint))
92 create_new_db = true;
95 if (create_new_db) {
96 if (connection != null)
97 connection.Dispose ();
98 File.Delete (GetDbPath (directory));
99 connection = Open (directory);
101 DoNonQuery ("CREATE TABLE db_info ( " +
102 " version INTEGER NOT NULL, " +
103 " fingerprint STRING NOT NULL " +
104 ")");
106 DoNonQuery ("INSERT INTO db_info (version, fingerprint) VALUES ({0}, '{1}')",
107 VERSION, index_fingerprint);
109 DoNonQuery ("CREATE TABLE file_attributes ( " +
110 " unique_id STRING UNIQUE, " +
111 " directory STRING NOT NULL, " +
112 " filename STRING NOT NULL, " +
113 " last_mtime STRING NOT NULL, " +
114 " last_attrtime STRING NOT NULL, " +
115 " filter_name STRING NOT NULL, " +
116 " filter_version STRING NOT NULL " +
117 ")");
118 } else {
119 SqliteCommand command;
120 SqliteDataReader reader;
121 int count = 0;
123 DateTime dt1 = DateTime.Now;
125 // Select all of the files and use them to populate our bit-vector.
126 command = new SqliteCommand ();
127 command.Connection = connection;
128 command.CommandText = "SELECT directory, filename FROM file_attributes";
130 reader = ExecuteReaderOrWait (command);
132 while (ReadOrWait (reader)) {
134 string dir = reader.GetString (0);
135 string file = reader.GetString (1);
136 string path = Path.Combine (dir, file);
137 SetPathFlag (path);
138 ++count;
141 reader.Close ();
142 command.Dispose ();
144 DateTime dt2 = DateTime.Now;
146 Logger.Log.Debug ("Loaded {0} records from {1} in {2:0.000}s",
147 count, GetDbPath (directory), (dt2 - dt1).TotalSeconds);
151 ///////////////////////////////////////////////////////////////////
153 private string GetDbPath (string directory)
155 return Path.Combine (directory, "FileAttributesStore.db");
158 private SqliteConnection Open (string directory)
160 SqliteConnection c;
161 c = new SqliteConnection ();
162 c.ConnectionString = "URI=file:" + GetDbPath (directory);
163 c.Open ();
164 return c;
167 private void DoNonQuery (string format, params object [] args)
169 SqliteCommand command;
170 command = new SqliteCommand ();
171 command.Connection = connection;
172 command.CommandText = String.Format (format, args);
174 while (true) {
175 try {
176 command.ExecuteNonQuery ();
177 break;
178 } catch (SqliteException ex) {
179 if (ex.SqliteError == SqliteError.BUSY)
180 Thread.Sleep (50);
181 else
182 throw ex;
186 command.Dispose ();
189 private SqliteCommand QueryCommand (string where_format, params object [] where_args)
191 SqliteCommand command;
192 command = new SqliteCommand ();
193 command.Connection = connection;
194 command.CommandText =
195 "SELECT unique_id, directory, filename, last_mtime, last_attrtime, filter_name, filter_version " +
196 "FROM file_attributes WHERE " +
197 String.Format (where_format, where_args);
198 return command;
201 static private SqliteDataReader ExecuteReaderOrWait (SqliteCommand command)
203 SqliteDataReader reader = null;
204 while (reader == null) {
205 try {
206 reader = command.ExecuteReader ();
207 } catch (SqliteException ex) {
208 if (ex.SqliteError == SqliteError.BUSY)
209 Thread.Sleep (50);
210 else
211 throw ex;
214 return reader;
217 static private bool ReadOrWait (SqliteDataReader reader)
219 while (true) {
220 try {
221 return reader.Read ();
222 } catch (SqliteException ex) {
223 if (ex.SqliteError == SqliteError.BUSY)
224 Thread.Sleep (50);
225 else
226 throw ex;
231 private FileAttributes GetFromReader (SqliteDataReader reader)
233 FileAttributes attr = new FileAttributes ();
235 attr.UniqueId = GuidFu.FromShortString (reader.GetString (0));
236 attr.Path = System.IO.Path.Combine (reader.GetString (1), reader.GetString (2));
237 attr.LastWriteTime = StringFu.StringToDateTime (reader.GetString (3));
238 attr.LastAttrTime = StringFu.StringToDateTime (reader.GetString (4));
239 attr.FilterName = reader.GetString (5);
240 attr.FilterVersion = int.Parse (reader.GetString (6));
242 if (attr.FilterName == "")
243 attr.FilterName = null;
245 return attr;
248 ///////////////////////////////////////////////////////////////////
250 private int GetPathHash (string path)
252 uint hash = 0xdeadbeef;
253 foreach (char c in path)
254 hash = 17 * hash + (uint) c;
255 // Fold the 32 bits in 16.
256 return (int) ((hash & 0xffff) ^ (hash >> 16));
259 private bool GetPathFlag (string path)
261 int hash = GetPathHash (path);
262 return path_flags [hash];
265 private void SetPathFlag (string path)
267 int hash = GetPathHash (path);
268 path_flags [hash] = true;
271 ///////////////////////////////////////////////////////////////////
273 public FileAttributes Read (string path)
275 SqliteCommand command;
276 SqliteDataReader reader;
278 if (! GetPathFlag (path))
279 return null;
281 FileAttributes attr = null;
282 bool found_too_many = false;
284 // We need to quote any 's that appear in the strings
285 // (int particular, in the path)
286 string directory = Path.GetDirectoryName (path).Replace ("'", "''");
287 string filename = Path.GetFileName (path).Replace ("'", "''");
288 lock (connection) {
289 command = QueryCommand ("directory='{0}' AND filename='{1}'",
290 directory, filename);
291 reader = ExecuteReaderOrWait (command);
293 if (ReadOrWait (reader)) {
294 attr = GetFromReader (reader);
296 if (ReadOrWait (reader))
297 found_too_many = true;
299 reader.Close ();
300 command.Dispose ();
302 // If we found more than one matching record for a given
303 // directory and filename, something has gone wrong.
304 // Since we have no way of knowing which one is correct
305 // and which isn't, we delete them all and return
306 // null. (Which in most cases will force a re-index.
307 if (found_too_many) {
308 DoNonQuery ("DELETE FROM file_attributes WHERE directory='{0}' AND filename='{1}'",
309 directory, filename);
313 return attr;
316 public bool Write (FileAttributes fa)
318 SetPathFlag (fa.Path);
320 // We need to quote any 's that appear in the strings
321 // (in particular, in the path)
322 lock (connection) {
324 // If a transaction has been requested, start it now.
325 MaybeStartTransaction ();
327 string filter_name;
328 filter_name = fa.FilterName;
329 if (filter_name == null)
330 filter_name = "";
331 filter_name = filter_name.Replace ("'", "''");
333 DoNonQuery ("INSERT OR REPLACE INTO file_attributes " +
334 " (unique_id, directory, filename, last_mtime, last_attrtime, filter_name, filter_version) " +
335 " VALUES ('{0}', '{1}', '{2}', '{3}', '{4}', '{5}', '{6}')",
336 GuidFu.ToShortString (fa.UniqueId),
337 fa.Directory.Replace ("'", "''"), fa.Filename.Replace ("'", "''"),
338 StringFu.DateTimeToString (fa.LastWriteTime),
339 StringFu.DateTimeToString (fa.LastAttrTime),
340 filter_name,
341 fa.FilterVersion);
343 return true;
346 public void Drop (string path)
348 // We don't want to "UnSetPathFlag" here, since we have no way of knowing
349 // if another path hashes to the same value as this one.
351 // We need to quote any 's that appear in the strings
352 // (in particular, in the path)
353 string directory = Path.GetDirectoryName (path).Replace ("'", "''");
354 string filename = Path.GetFileName (path).Replace ("'", "''");
355 lock (connection) {
357 // If a transaction has been requested, start it now.
358 MaybeStartTransaction ();
360 DoNonQuery ("DELETE FROM file_attributes WHERE directory='{0}' AND filename='{1}'",
361 directory, filename);
365 private void MaybeStartTransaction ()
367 if (transaction_state == TransactionState.Requested) {
368 DoNonQuery ("BEGIN");
369 transaction_state = TransactionState.Started;
373 public void BeginTransaction ()
375 if (transaction_state == TransactionState.None)
376 transaction_state = TransactionState.Requested;
379 public void CommitTransaction ()
381 if (transaction_state == TransactionState.Started) {
382 lock (connection)
383 DoNonQuery ("COMMIT");
385 transaction_state = TransactionState.None;
388 public void Flush ()
390 lock (connection) {
391 if (transaction_count > 0) {
392 Logger.Log.Debug ("Flushing requested -- committing sqlite transaction");
393 DoNonQuery ("COMMIT");
394 transaction_count = 0;
399 ///////////////////////////////////////////////////////////////////
401 // Return all attributes in the attributes database, used for merging
403 private ICollection ReadAllAttributes ()
405 ArrayList attributes = new ArrayList ();
407 SqliteCommand command;
408 SqliteDataReader reader;
410 lock (connection) {
411 command = new SqliteCommand ();
412 command.Connection = connection;
413 command.CommandText =
414 "SELECT unique_id, directory, filename, last_mtime, last_attrtime, filter_name, filter_version " +
415 "FROM file_attributes";
417 reader = ExecuteReaderOrWait (command);
419 while (ReadOrWait (reader)) {
420 attributes.Add (GetFromReader (reader));
422 reader.Close ();
423 command.Dispose ();
426 return attributes;
429 // FIXME: Might wanna do this a bit more intelligently
431 public void Merge (FileAttributesStore_Sqlite fa_sqlite_store_to_merge)
433 ICollection attributes = fa_sqlite_store_to_merge.ReadAllAttributes ();
435 BeginTransaction ();
437 foreach (FileAttributes attribute in attributes)
438 Write (attribute);
440 CommitTransaction ();