Oops, fix a broken part of the patch
[beagle.git] / beagled / FileAttributesStore_Sqlite.cs
blob2b6dbbf30416f9fbd8f5c44cde3b5c36968274d4
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 {
65 // Funky logic here to deal with sqlite versions.
67 // When sqlite 3 tries to open an sqlite 2 database,
68 // it will throw an SqliteException with SqliteError
69 // NOTADB when trying to execute a command.
71 // When sqlite 2 tries to open an sqlite 3 database,
72 // it will throw an ApplicationException when it
73 // tries to open the database.
75 try {
76 connection = Open (directory);
77 } catch (ApplicationException) {
78 Logger.Log.Warn ("Likely sqlite database version mismatch trying to open {0}. Purging.", GetDbPath (directory));
79 create_new_db = true;
82 if (! create_new_db) {
83 SqliteCommand command;
84 SqliteDataReader reader = null;
85 int stored_version = 0;
86 string stored_fingerprint = null;
89 command = new SqliteCommand ();
90 command.Connection = connection;
91 command.CommandText =
92 "SELECT version, fingerprint FROM db_info";
93 try {
94 reader = ExecuteReaderOrWait (command);
95 } catch (Exception ex) {
96 Logger.Log.Warn ("Likely sqlite database version mismatch trying to read from {0}. Purging.", GetDbPath (directory));
97 create_new_db = true;
99 if (reader != null && ! create_new_db) {
100 if (ReadOrWait (reader)) {
101 stored_version = reader.GetInt32 (0);
102 stored_fingerprint = reader.GetString (1);
104 reader.Close ();
106 command.Dispose ();
108 if (VERSION != stored_version
109 || (index_fingerprint != null && index_fingerprint != stored_fingerprint))
110 create_new_db = true;
114 if (create_new_db) {
115 if (connection != null)
116 connection.Dispose ();
117 File.Delete (GetDbPath (directory));
118 connection = Open (directory);
120 DoNonQuery ("CREATE TABLE db_info ( " +
121 " version INTEGER NOT NULL, " +
122 " fingerprint STRING NOT NULL " +
123 ")");
125 DoNonQuery ("INSERT INTO db_info (version, fingerprint) VALUES ({0}, '{1}')",
126 VERSION, index_fingerprint);
128 DoNonQuery ("CREATE TABLE file_attributes ( " +
129 " unique_id STRING UNIQUE, " +
130 " directory STRING NOT NULL, " +
131 " filename STRING NOT NULL, " +
132 " last_mtime STRING NOT NULL, " +
133 " last_attrtime STRING NOT NULL, " +
134 " filter_name STRING NOT NULL, " +
135 " filter_version STRING NOT NULL " +
136 ")");
137 } else {
138 SqliteCommand command;
139 SqliteDataReader reader;
140 int count = 0;
142 DateTime dt1 = DateTime.Now;
144 // Select all of the files and use them to populate our bit-vector.
145 command = new SqliteCommand ();
146 command.Connection = connection;
147 command.CommandText = "SELECT directory, filename FROM file_attributes";
149 reader = ExecuteReaderOrWait (command);
151 while (ReadOrWait (reader)) {
153 string dir = reader.GetString (0);
154 string file = reader.GetString (1);
155 string path = Path.Combine (dir, file);
156 SetPathFlag (path);
157 ++count;
160 reader.Close ();
161 command.Dispose ();
163 DateTime dt2 = DateTime.Now;
165 Logger.Log.Debug ("Loaded {0} records from {1} in {2:0.000}s",
166 count, GetDbPath (directory), (dt2 - dt1).TotalSeconds);
170 ///////////////////////////////////////////////////////////////////
172 private string GetDbPath (string directory)
174 return Path.Combine (directory, "FileAttributesStore.db");
177 private SqliteConnection Open (string directory)
179 SqliteConnection c;
180 c = new SqliteConnection ();
181 c.ConnectionString = "version=" + ExternalStringsHack.SqliteVersion
182 + ",encoding=UTF-8,URI=file:" + GetDbPath (directory);
183 c.Open ();
184 return c;
187 private void DoNonQuery (string format, params object [] args)
189 SqliteCommand command;
190 command = new SqliteCommand ();
191 command.Connection = connection;
192 command.CommandText = String.Format (format, args);
194 while (true) {
195 try {
196 command.ExecuteNonQuery ();
197 break;
198 } catch (SqliteBusyException ex) {
199 Thread.Sleep (50);
203 command.Dispose ();
206 private SqliteCommand QueryCommand (string where_format, params object [] where_args)
208 SqliteCommand command;
209 command = new SqliteCommand ();
210 command.Connection = connection;
211 command.CommandText =
212 "SELECT unique_id, directory, filename, last_mtime, last_attrtime, filter_name, filter_version " +
213 "FROM file_attributes WHERE " +
214 String.Format (where_format, where_args);
215 return command;
218 static private SqliteDataReader ExecuteReaderOrWait (SqliteCommand command)
220 SqliteDataReader reader = null;
221 while (reader == null) {
222 try {
223 reader = command.ExecuteReader ();
224 } catch (SqliteBusyException ex) {
225 Thread.Sleep (50);
228 return reader;
231 static private bool ReadOrWait (SqliteDataReader reader)
233 while (true) {
234 try {
235 return reader.Read ();
236 } catch (SqliteBusyException ex) {
237 Thread.Sleep (50);
242 private FileAttributes GetFromReader (SqliteDataReader reader)
244 FileAttributes attr = new FileAttributes ();
246 attr.UniqueId = GuidFu.FromShortString (reader.GetString (0));
247 attr.Path = System.IO.Path.Combine (reader.GetString (1), reader.GetString (2));
248 attr.LastWriteTime = StringFu.StringToDateTime (reader.GetString (3));
249 attr.LastAttrTime = StringFu.StringToDateTime (reader.GetString (4));
250 attr.FilterName = reader.GetString (5);
251 attr.FilterVersion = int.Parse (reader.GetString (6));
253 if (attr.FilterName == "")
254 attr.FilterName = null;
256 return attr;
259 ///////////////////////////////////////////////////////////////////
261 private int GetPathHash (string path)
263 uint hash = 0xdeadbeef;
264 foreach (char c in path)
265 hash = 17 * hash + (uint) c;
266 // Fold the 32 bits in 16.
267 return (int) ((hash & 0xffff) ^ (hash >> 16));
270 private bool GetPathFlag (string path)
272 int hash = GetPathHash (path);
273 return path_flags [hash];
276 private void SetPathFlag (string path)
278 int hash = GetPathHash (path);
279 path_flags [hash] = true;
282 ///////////////////////////////////////////////////////////////////
284 public FileAttributes Read (string path)
286 SqliteCommand command;
287 SqliteDataReader reader;
289 if (! GetPathFlag (path))
290 return null;
292 FileAttributes attr = null;
293 bool found_too_many = false;
295 // We need to quote any 's that appear in the strings
296 // (int particular, in the path)
297 string directory = FileSystem.GetDirectoryNameRootOk (path).Replace ("'", "''");
298 string filename = Path.GetFileName (path).Replace ("'", "''");
299 lock (connection) {
300 command = QueryCommand ("directory='{0}' AND filename='{1}'",
301 directory, filename);
302 reader = ExecuteReaderOrWait (command);
304 if (ReadOrWait (reader)) {
305 attr = GetFromReader (reader);
307 if (ReadOrWait (reader))
308 found_too_many = true;
310 reader.Close ();
311 command.Dispose ();
313 // If we found more than one matching record for a given
314 // directory and filename, something has gone wrong.
315 // Since we have no way of knowing which one is correct
316 // and which isn't, we delete them all and return
317 // null. (Which in most cases will force a re-index.
318 if (found_too_many) {
319 DoNonQuery ("DELETE FROM file_attributes WHERE directory='{0}' AND filename='{1}'",
320 directory, filename);
324 return attr;
327 public bool Write (FileAttributes fa)
329 SetPathFlag (fa.Path);
331 // We need to quote any 's that appear in the strings
332 // (in particular, in the path)
333 lock (connection) {
335 // If a transaction has been requested, start it now.
336 MaybeStartTransaction ();
338 string filter_name;
339 filter_name = fa.FilterName;
340 if (filter_name == null)
341 filter_name = "";
342 filter_name = filter_name.Replace ("'", "''");
344 DoNonQuery ("INSERT OR REPLACE INTO file_attributes " +
345 " (unique_id, directory, filename, last_mtime, last_attrtime, filter_name, filter_version) " +
346 " VALUES ('{0}', '{1}', '{2}', '{3}', '{4}', '{5}', '{6}')",
347 GuidFu.ToShortString (fa.UniqueId),
348 fa.Directory.Replace ("'", "''"), fa.Filename.Replace ("'", "''"),
349 StringFu.DateTimeToString (fa.LastWriteTime),
350 StringFu.DateTimeToString (fa.LastAttrTime),
351 filter_name,
352 fa.FilterVersion);
354 return true;
357 public void Drop (string path)
359 // We don't want to "UnSetPathFlag" here, since we have no way of knowing
360 // if another path hashes to the same value as this one.
362 // We need to quote any 's that appear in the strings
363 // (in particular, in the path)
364 string directory = FileSystem.GetDirectoryNameRootOk (path).Replace ("'", "''");
365 string filename = Path.GetFileName (path).Replace ("'", "''");
366 lock (connection) {
368 // If a transaction has been requested, start it now.
369 MaybeStartTransaction ();
371 DoNonQuery ("DELETE FROM file_attributes WHERE directory='{0}' AND filename='{1}'",
372 directory, filename);
376 private void MaybeStartTransaction ()
378 if (transaction_state == TransactionState.Requested) {
379 DoNonQuery ("BEGIN");
380 transaction_state = TransactionState.Started;
384 public void BeginTransaction ()
386 if (transaction_state == TransactionState.None)
387 transaction_state = TransactionState.Requested;
390 public void CommitTransaction ()
392 if (transaction_state == TransactionState.Started) {
393 lock (connection)
394 DoNonQuery ("COMMIT");
396 transaction_state = TransactionState.None;
399 public void Flush ()
401 lock (connection) {
402 if (transaction_count > 0) {
403 Logger.Log.Debug ("Flushing requested -- committing sqlite transaction");
404 DoNonQuery ("COMMIT");
405 transaction_count = 0;
410 ///////////////////////////////////////////////////////////////////
412 // Return all attributes in the attributes database, used for merging
414 private ICollection ReadAllAttributes ()
416 ArrayList attributes = new ArrayList ();
418 SqliteCommand command;
419 SqliteDataReader reader;
421 lock (connection) {
422 command = new SqliteCommand ();
423 command.Connection = connection;
424 command.CommandText =
425 "SELECT unique_id, directory, filename, last_mtime, last_attrtime, filter_name, filter_version " +
426 "FROM file_attributes";
428 reader = ExecuteReaderOrWait (command);
430 while (ReadOrWait (reader)) {
431 attributes.Add (GetFromReader (reader));
433 reader.Close ();
434 command.Dispose ();
437 return attributes;
440 // FIXME: Might wanna do this a bit more intelligently
442 public void Merge (FileAttributesStore_Sqlite fa_sqlite_store_to_merge)
444 ICollection attributes = fa_sqlite_store_to_merge.ReadAllAttributes ();
446 BeginTransaction ();
448 foreach (FileAttributes attribute in attributes)
449 Write (attribute);
451 CommitTransaction ();