* tools/Info.cs: Add --list-backends, --list-static-indexes to
[beagle.git] / beagled / FileAttributesStore_Sqlite.cs
blob8ec57aa8ded5d26f82a83c8786780d8362c85275
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 ")");
138 DoNonQuery ("CREATE INDEX file_path on file_attributes (" +
139 " directory, " +
140 " filename " +
141 ")");
142 } else {
143 SqliteCommand command;
144 SqliteDataReader reader;
145 int count = 0;
147 DateTime dt1 = DateTime.Now;
149 // Select all of the files and use them to populate our bit-vector.
150 command = new SqliteCommand ();
151 command.Connection = connection;
152 command.CommandText = "SELECT directory, filename FROM file_attributes";
154 reader = ExecuteReaderOrWait (command);
156 while (ReadOrWait (reader)) {
158 string dir = reader.GetString (0);
159 string file = reader.GetString (1);
160 string path = Path.Combine (dir, file);
161 SetPathFlag (path);
162 ++count;
165 reader.Close ();
166 command.Dispose ();
168 DateTime dt2 = DateTime.Now;
170 Logger.Log.Debug ("Loaded {0} records from {1} in {2:0.000}s",
171 count, GetDbPath (directory), (dt2 - dt1).TotalSeconds);
175 ///////////////////////////////////////////////////////////////////
177 private string GetDbPath (string directory)
179 return Path.Combine (directory, "FileAttributesStore.db");
182 private SqliteConnection Open (string directory)
184 SqliteConnection c;
185 c = new SqliteConnection ();
186 c.ConnectionString = "version=" + ExternalStringsHack.SqliteVersion
187 + ",encoding=UTF-8,URI=file:" + GetDbPath (directory);
188 c.Open ();
189 return c;
192 private void DoNonQuery (string format, params object [] args)
194 SqliteCommand command;
195 command = new SqliteCommand ();
196 command.Connection = connection;
197 command.CommandText = String.Format (format, args);
199 while (true) {
200 try {
201 command.ExecuteNonQuery ();
202 break;
203 } catch (SqliteBusyException ex) {
204 Thread.Sleep (50);
208 command.Dispose ();
211 private SqliteCommand QueryCommand (string where_format, params object [] where_args)
213 SqliteCommand command;
214 command = new SqliteCommand ();
215 command.Connection = connection;
216 command.CommandText =
217 "SELECT unique_id, directory, filename, last_mtime, last_attrtime, filter_name, filter_version " +
218 "FROM file_attributes WHERE " +
219 String.Format (where_format, where_args);
220 return command;
223 static private SqliteDataReader ExecuteReaderOrWait (SqliteCommand command)
225 SqliteDataReader reader = null;
226 while (reader == null) {
227 try {
228 reader = command.ExecuteReader ();
229 } catch (SqliteBusyException ex) {
230 Thread.Sleep (50);
233 return reader;
236 static private bool ReadOrWait (SqliteDataReader reader)
238 while (true) {
239 try {
240 return reader.Read ();
241 } catch (SqliteBusyException ex) {
242 Thread.Sleep (50);
247 private FileAttributes GetFromReader (SqliteDataReader reader)
249 FileAttributes attr = new FileAttributes ();
251 attr.UniqueId = GuidFu.FromShortString (reader.GetString (0));
252 attr.Path = System.IO.Path.Combine (reader.GetString (1), reader.GetString (2));
253 attr.LastWriteTime = StringFu.StringToDateTime (reader.GetString (3));
254 attr.LastAttrTime = StringFu.StringToDateTime (reader.GetString (4));
255 attr.FilterName = reader.GetString (5);
256 attr.FilterVersion = int.Parse (reader.GetString (6));
258 if (attr.FilterName == "")
259 attr.FilterName = null;
261 return attr;
264 ///////////////////////////////////////////////////////////////////
266 private int GetPathHash (string path)
268 uint hash = 0xdeadbeef;
269 foreach (char c in path)
270 hash = 17 * hash + (uint) c;
271 // Fold the 32 bits in 16.
272 return (int) ((hash & 0xffff) ^ (hash >> 16));
275 private bool GetPathFlag (string path)
277 int hash = GetPathHash (path);
278 return path_flags [hash];
281 private void SetPathFlag (string path)
283 int hash = GetPathHash (path);
284 path_flags [hash] = true;
287 ///////////////////////////////////////////////////////////////////
289 public FileAttributes Read (string path)
291 SqliteCommand command;
292 SqliteDataReader reader;
294 if (! GetPathFlag (path))
295 return null;
297 FileAttributes attr = null;
298 bool found_too_many = false;
300 // We need to quote any 's that appear in the strings
301 // (int particular, in the path)
302 string directory = FileSystem.GetDirectoryNameRootOk (path).Replace ("'", "''");
303 string filename = Path.GetFileName (path).Replace ("'", "''");
304 lock (connection) {
305 command = QueryCommand ("directory='{0}' AND filename='{1}'",
306 directory, filename);
307 reader = ExecuteReaderOrWait (command);
309 if (ReadOrWait (reader)) {
310 attr = GetFromReader (reader);
312 if (ReadOrWait (reader))
313 found_too_many = true;
315 reader.Close ();
316 command.Dispose ();
318 // If we found more than one matching record for a given
319 // directory and filename, something has gone wrong.
320 // Since we have no way of knowing which one is correct
321 // and which isn't, we delete them all and return
322 // null. (Which in most cases will force a re-index.
323 if (found_too_many) {
324 DoNonQuery ("DELETE FROM file_attributes WHERE directory='{0}' AND filename='{1}'",
325 directory, filename);
329 return attr;
332 public bool Write (FileAttributes fa)
334 SetPathFlag (fa.Path);
336 // We need to quote any 's that appear in the strings
337 // (in particular, in the path)
338 lock (connection) {
340 // If a transaction has been requested, start it now.
341 MaybeStartTransaction ();
343 string filter_name;
344 filter_name = fa.FilterName;
345 if (filter_name == null)
346 filter_name = "";
347 filter_name = filter_name.Replace ("'", "''");
349 DoNonQuery ("INSERT OR REPLACE INTO file_attributes " +
350 " (unique_id, directory, filename, last_mtime, last_attrtime, filter_name, filter_version) " +
351 " VALUES ('{0}', '{1}', '{2}', '{3}', '{4}', '{5}', '{6}')",
352 GuidFu.ToShortString (fa.UniqueId),
353 fa.Directory.Replace ("'", "''"), fa.Filename.Replace ("'", "''"),
354 StringFu.DateTimeToString (fa.LastWriteTime),
355 StringFu.DateTimeToString (fa.LastAttrTime),
356 filter_name,
357 fa.FilterVersion);
359 return true;
362 public void Drop (string path)
364 // We don't want to "UnSetPathFlag" here, since we have no way of knowing
365 // if another path hashes to the same value as this one.
367 // We need to quote any 's that appear in the strings
368 // (in particular, in the path)
369 string directory = FileSystem.GetDirectoryNameRootOk (path).Replace ("'", "''");
370 string filename = Path.GetFileName (path).Replace ("'", "''");
371 lock (connection) {
373 // If a transaction has been requested, start it now.
374 MaybeStartTransaction ();
376 DoNonQuery ("DELETE FROM file_attributes WHERE directory='{0}' AND filename='{1}'",
377 directory, filename);
381 private void MaybeStartTransaction ()
383 if (transaction_state == TransactionState.Requested) {
384 DoNonQuery ("BEGIN");
385 transaction_state = TransactionState.Started;
389 public void BeginTransaction ()
391 if (transaction_state == TransactionState.None)
392 transaction_state = TransactionState.Requested;
395 public void CommitTransaction ()
397 if (transaction_state == TransactionState.Started) {
398 lock (connection)
399 DoNonQuery ("COMMIT");
401 transaction_state = TransactionState.None;
404 public void Flush ()
406 lock (connection) {
407 if (transaction_count > 0) {
408 Logger.Log.Debug ("Flushing requested -- committing sqlite transaction");
409 DoNonQuery ("COMMIT");
410 transaction_count = 0;
415 ///////////////////////////////////////////////////////////////////
417 // Return all attributes in the attributes database, used for merging
419 private ICollection ReadAllAttributes ()
421 ArrayList attributes = new ArrayList ();
423 SqliteCommand command;
424 SqliteDataReader reader;
426 lock (connection) {
427 command = new SqliteCommand ();
428 command.Connection = connection;
429 command.CommandText =
430 "SELECT unique_id, directory, filename, last_mtime, last_attrtime, filter_name, filter_version " +
431 "FROM file_attributes";
433 reader = ExecuteReaderOrWait (command);
435 while (ReadOrWait (reader)) {
436 attributes.Add (GetFromReader (reader));
438 reader.Close ();
439 command.Dispose ();
442 return attributes;
445 // FIXME: Might wanna do this a bit more intelligently
447 public void Merge (FileAttributesStore_Sqlite fa_sqlite_store_to_merge)
449 ICollection attributes = fa_sqlite_store_to_merge.ReadAllAttributes ();
451 BeginTransaction ();
453 foreach (FileAttributes attribute in attributes)
454 Write (attribute);
456 CommitTransaction ();