2 // FileAttributesStore_Sqlite.cs
4 // Copyright (C) 2004 Novell, Inc.
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
28 using System
.Collections
;
30 using System
.Threading
;
32 using Mono
.Data
.SqliteClient
;
36 namespace Beagle
.Daemon
{
38 public class FileAttributesStore_Sqlite
: IFileAttributesStore
{
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
{
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
))) {
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
;
75 "SELECT version, fingerprint FROM db_info";
77 reader
= ExecuteReaderOrWait (command
);
78 } catch (Exception ex
) {
81 if (reader
!= null && ! create_new_db
) {
82 if (ReadOrWait (reader
)) {
83 stored_version
= reader
.GetInt32 (0);
84 stored_fingerprint
= reader
.GetString (1);
90 if (VERSION
!= stored_version
91 || (index_fingerprint
!= null && index_fingerprint
!= stored_fingerprint
))
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 " +
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 " +
119 SqliteCommand command
;
120 SqliteDataReader reader
;
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
);
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
)
161 c
= new SqliteConnection ();
162 c
.ConnectionString
= "URI=file:" + GetDbPath (directory
);
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
);
176 command
.ExecuteNonQuery ();
178 } catch (SqliteException ex
) {
179 if (ex
.SqliteError
== SqliteError
.BUSY
)
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
);
201 static private SqliteDataReader
ExecuteReaderOrWait (SqliteCommand command
)
203 SqliteDataReader reader
= null;
204 while (reader
== null) {
206 reader
= command
.ExecuteReader ();
207 } catch (SqliteException ex
) {
208 if (ex
.SqliteError
== SqliteError
.BUSY
)
217 static private bool ReadOrWait (SqliteDataReader reader
)
221 return reader
.Read ();
222 } catch (SqliteException ex
) {
223 if (ex
.SqliteError
== SqliteError
.BUSY
)
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;
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
))
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 ("'", "''");
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;
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
);
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)
324 // If a transaction has been requested, start it now.
325 MaybeStartTransaction ();
328 filter_name
= fa
.FilterName
;
329 if (filter_name
== null)
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
),
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 ("'", "''");
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
) {
383 DoNonQuery ("COMMIT");
385 transaction_state
= TransactionState
.None
;
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
;
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
));
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 ();
437 foreach (FileAttributes attribute
in attributes
)
440 CommitTransaction ();