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
{
40 const int VERSION
= 1;
42 private SqliteConnection connection
;
43 private BitArray path_flags
;
44 private int transaction_count
= 0;
46 public FileAttributesStore_Sqlite (string directory
, string index_fingerprint
)
48 bool create_new_db
= false;
49 path_flags
= new BitArray (65536);
51 if (! File
.Exists (GetDbPath (directory
))) {
54 connection
= Open (directory
);
56 SqliteCommand command
;
57 SqliteDataReader reader
= null;
58 int stored_version
= 0;
59 string stored_fingerprint
= null;
62 command
= new SqliteCommand ();
63 command
.Connection
= connection
;
65 "SELECT version, fingerprint FROM db_info";
67 reader
= ExecuteReaderOrWait (command
);
68 } catch (Exception ex
) {
71 if (reader
!= null && ! create_new_db
) {
72 if (ReadOrWait (reader
)) {
73 stored_version
= reader
.GetInt32 (0);
74 stored_fingerprint
= reader
.GetString (1);
80 if (VERSION
!= stored_version
81 || (index_fingerprint
!= null && index_fingerprint
!= stored_fingerprint
))
86 if (connection
!= null)
87 connection
.Dispose ();
88 File
.Delete (GetDbPath (directory
));
89 connection
= Open (directory
);
91 DoNonQuery ("CREATE TABLE db_info ( " +
92 " version INTEGER NOT NULL, " +
93 " fingerprint STRING NOT NULL " +
96 DoNonQuery ("INSERT INTO db_info (version, fingerprint) VALUES ({0}, '{1}')",
97 VERSION
, index_fingerprint
);
99 DoNonQuery ("CREATE TABLE file_attributes ( " +
100 " unique_id STRING UNIQUE, " +
101 " directory STRING NOT NULL, " +
102 " filename STRING NOT NULL, " +
103 " last_mtime STRING NOT NULL, " +
104 " last_indexed STRING NOT NULL, " +
105 " filter_name STRING NOT NULL, " +
106 " filter_version STRING NOT NULL " +
109 SqliteCommand command
;
110 SqliteDataReader reader
;
113 DateTime dt1
= DateTime
.Now
;
115 // Select all of the files and use them to populate our bit-vector.
116 command
= new SqliteCommand ();
117 command
.Connection
= connection
;
118 command
.CommandText
= "SELECT directory, filename FROM file_attributes";
120 reader
= ExecuteReaderOrWait (command
);
122 while (ReadOrWait (reader
)) {
124 string dir
= reader
.GetString (0);
125 string file
= reader
.GetString (1);
126 string path
= Path
.Combine (dir
, file
);
134 DateTime dt2
= DateTime
.Now
;
136 Logger
.Log
.Debug ("Loaded {0} records from {1} in {2:0.000}s",
137 count
, GetDbPath (directory
), (dt2
- dt1
).TotalSeconds
);
140 Shutdown
.ShutdownEvent
+= Flush
;
143 ///////////////////////////////////////////////////////////////////
145 private string GetDbPath (string directory
)
147 return Path
.Combine (directory
, "FileAttributesStore.db");
150 private SqliteConnection
Open (string directory
)
153 c
= new SqliteConnection ();
154 c
.ConnectionString
= "URI=file:" + GetDbPath (directory
);
159 private void DoNonQuery (string format
, params object [] args
)
161 SqliteCommand command
;
162 command
= new SqliteCommand ();
163 command
.Connection
= connection
;
164 command
.CommandText
= String
.Format (format
, args
);
168 command
.ExecuteNonQuery ();
170 } catch (SqliteException ex
) {
171 if (ex
.SqliteError
== SqliteError
.BUSY
)
181 private SqliteCommand
QueryCommand (string where_format
, params object [] where_args
)
183 SqliteCommand command
;
184 command
= new SqliteCommand ();
185 command
.Connection
= connection
;
186 command
.CommandText
=
187 "SELECT unique_id, directory, filename, last_mtime, last_indexed, filter_name, filter_version " +
188 "FROM file_attributes WHERE " +
189 String
.Format (where_format
, where_args
);
193 static private SqliteDataReader
ExecuteReaderOrWait (SqliteCommand command
)
195 SqliteDataReader reader
= null;
196 while (reader
== null) {
198 reader
= command
.ExecuteReader ();
199 } catch (SqliteException ex
) {
200 if (ex
.SqliteError
== SqliteError
.BUSY
)
209 static private bool ReadOrWait (SqliteDataReader reader
)
213 return reader
.Read ();
214 } catch (SqliteException ex
) {
215 if (ex
.SqliteError
== SqliteError
.BUSY
)
223 private FileAttributes
GetFromReader (SqliteDataReader reader
)
225 FileAttributes attr
= new FileAttributes ();
227 attr
.UniqueId
= GuidFu
.FromShortString (reader
.GetString (0));
228 attr
.Path
= System
.IO
.Path
.Combine (reader
.GetString (1), reader
.GetString (2));
229 attr
.LastWriteTime
= StringFu
.StringToDateTime (reader
.GetString (3));
230 attr
.LastIndexedTime
= StringFu
.StringToDateTime (reader
.GetString (4));
231 attr
.FilterName
= reader
.GetString (5);
232 attr
.FilterVersion
= int.Parse (reader
.GetString (6));
237 ///////////////////////////////////////////////////////////////////
239 private int GetPathHash (string path
)
241 uint hash
= 0xdeadbeef;
242 foreach (char c
in path
)
243 hash
= 17 * hash
+ (uint) c
;
244 // Fold the 32 bits in 16.
245 return (int) ((hash
& 0xffff) ^
(hash
>> 16));
248 private bool GetPathFlag (string path
)
250 int hash
= GetPathHash (path
);
251 return path_flags
[hash
];
254 private void SetPathFlag (string path
)
256 int hash
= GetPathHash (path
);
257 path_flags
[hash
] = true;
260 ///////////////////////////////////////////////////////////////////
262 public FileAttributes
Read (string path
)
264 SqliteCommand command
;
265 SqliteDataReader reader
;
267 if (! GetPathFlag (path
))
270 FileAttributes attr
= null;
271 bool found_too_many
= false;
273 // We need to quote any 's that appear in the strings
274 // (int particular, in the path)
275 string directory
= Path
.GetDirectoryName (path
).Replace ("'", "''");
276 string filename
= Path
.GetFileName (path
).Replace ("'", "''");
278 command
= QueryCommand ("directory='{0}' AND filename='{1}'",
279 directory
, filename
);
280 reader
= ExecuteReaderOrWait (command
);
282 if (ReadOrWait (reader
)) {
283 attr
= GetFromReader (reader
);
285 if (ReadOrWait (reader
))
286 found_too_many
= true;
291 // If we found more than one matching record for a given
292 // directory and filename, something has gone wrong.
293 // Since we have no way of knowing which one is correct
294 // and which isn't, we delete them all and return
295 // null. (Which in most cases will force a re-index.
296 if (found_too_many
) {
297 DoNonQuery ("DELETE FROM file_attributes WHERE directory='{0}' AND filename='{1}'",
298 directory
, filename
);
305 public bool Write (FileAttributes fa
)
307 SetPathFlag (fa
.Path
);
309 // We need to quote any 's that appear in the strings
310 // (in particular, in the path)
312 if (transaction_count
== 0 && ! Shutdown
.ShutdownRequested
) {
313 Logger
.Log
.Debug ("Beginning sqlite transaction");
314 DoNonQuery ("BEGIN");
317 DoNonQuery ("INSERT OR REPLACE INTO file_attributes " +
318 " (unique_id, directory, filename, last_mtime, last_indexed, filter_name, filter_version) " +
319 " VALUES ('{0}', '{1}', '{2}', '{3}', '{4}', '{5}', '{6}')",
320 GuidFu
.ToShortString (fa
.UniqueId
),
321 fa
.Directory
.Replace ("'", "''"), fa
.Filename
.Replace ("'", "''"),
322 StringFu
.DateTimeToString (fa
.LastWriteTime
),
323 StringFu
.DateTimeToString (fa
.LastIndexedTime
),
327 if (! Shutdown
.ShutdownRequested
)
330 // 150 is a pretty arbitrary number
331 if (transaction_count
== 150) {
332 Logger
.Log
.Debug ("Committing sqlite transaction");
333 DoNonQuery ("COMMIT");
334 transaction_count
= 0;
340 public void Drop (string path
)
342 // We don't want to "UnSetPathFlag" here, since we have no way of knowing
343 // if another path hashes to the same value as this one.
345 // We need to quote any 's that appear in the strings
346 // (in particular, in the path)
347 string directory
= Path
.GetDirectoryName (path
).Replace ("'", "''");
348 string filename
= Path
.GetFileName (path
).Replace ("'", "''");
350 DoNonQuery ("DELETE FROM file_attributes WHERE directory='{0}' AND filename='{1}'",
351 directory
, filename
);
358 if (transaction_count
> 0) {
359 Logger
.Log
.Debug ("Flushing requested -- committing sqlite transaction");
360 DoNonQuery ("COMMIT");
361 transaction_count
= 0;
366 ///////////////////////////////////////////////////////////////////
368 // Return all attributes in the attributes database, used for merging
370 private ICollection
ReadAllAttributes ()
372 ArrayList attributes
= new ArrayList ();
374 SqliteCommand command
;
375 SqliteDataReader reader
;
377 FileAttributes attr
= null;
380 command
= new SqliteCommand ();
381 command
.Connection
= connection
;
382 command
.CommandText
=
383 "SELECT unique_id, directory, filename, last_mtime, last_indexed, filter_name, filter_version " +
384 "FROM file_attributes";
386 reader
= ExecuteReaderOrWait (command
);
388 while (ReadOrWait (reader
)) {
389 attributes
.Add (GetFromReader (reader
));
398 // FIXME: Might wanna do this a bit more intelligently
400 public void Merge (FileAttributesStore_Sqlite fa_sqlite_store_to_merge
)
402 ICollection attributes
= fa_sqlite_store_to_merge
.ReadAllAttributes ();
404 foreach (FileAttributes attribute
in attributes
)