2005-05-14 Gabor Kelemen <kelemeng@gnome.hu>
[beagle.git] / beagled / FileAttributesStore_Sqlite.cs
blob304748dfa79c5083e8ddb51afb5b32305e35f242
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;
31 using Mono.Data.SqliteClient;
33 using Beagle.Util;
35 namespace Beagle.Daemon {
37 public class FileAttributesStore_Sqlite : IFileAttributesStore {
39 const int VERSION = 1;
41 private SqliteConnection connection;
42 private byte[] path_flags;
44 public FileAttributesStore_Sqlite (string directory, string index_fingerprint)
46 bool create_new_db = false;
48 path_flags = new byte [8192];
50 if (! File.Exists (GetDbPath (directory))) {
51 create_new_db = true;
52 } else {
53 connection = Open (directory);
55 SqliteCommand command;
56 SqliteDataReader reader = null;
57 int stored_version = 0;
58 string stored_fingerprint = null;
61 command = new SqliteCommand ();
62 command.Connection = connection;
63 command.CommandText =
64 "SELECT version, fingerprint FROM db_info";
65 try {
66 reader = command.ExecuteReader ();
67 } catch (Exception ex) {
68 create_new_db = true;
70 if (reader != null && ! create_new_db) {
71 if (reader.Read ()) {
72 stored_version = reader.GetInt32 (0);
73 stored_fingerprint = reader.GetString (1);
75 reader.Close ();
77 command.Dispose ();
79 if (VERSION != stored_version
80 || (index_fingerprint != null && index_fingerprint != stored_fingerprint))
81 create_new_db = true;
84 if (create_new_db) {
85 if (connection != null)
86 connection.Dispose ();
87 File.Delete (GetDbPath (directory));
88 connection = Open (directory);
90 DoNonQuery ("CREATE TABLE db_info ( " +
91 " version INTEGER NOT NULL, " +
92 " fingerprint STRING NOT NULL " +
93 ")");
95 DoNonQuery ("INSERT INTO db_info (version, fingerprint) VALUES ({0}, '{1}')",
96 VERSION, index_fingerprint);
98 DoNonQuery ("CREATE TABLE file_attributes ( " +
99 " unique_id STRING UNIQUE, " +
100 " directory STRING NOT NULL, " +
101 " filename STRING NOT NULL, " +
102 " last_mtime STRING NOT NULL, " +
103 " last_indexed STRING NOT NULL, " +
104 " filter_name STRING NOT NULL, " +
105 " filter_version STRING NOT NULL " +
106 ")");
107 } else {
108 SqliteCommand command;
109 SqliteDataReader reader;
110 int count = 0;
112 DateTime dt1 = DateTime.Now;
114 // Select all of the files and use them to populate our bit-vector.
115 command = new SqliteCommand ();
116 command.Connection = connection;
117 command.CommandText = "SELECT directory, filename FROM file_attributes";
118 reader = command.ExecuteReader ();
120 while (reader.Read ()) {
121 string dir = reader.GetString (0);
122 string file = reader.GetString (1);
123 string path = Path.Combine (dir, file);
124 SetPathFlag (path, true);
125 ++count;
127 reader.Close ();
128 command.Dispose ();
130 DateTime dt2 = DateTime.Now;
132 Logger.Log.Info ("Loaded {0} records from {1} in {2:0.000}s",
133 count, GetDbPath (directory), (dt2 - dt1).TotalSeconds);
137 ///////////////////////////////////////////////////////////////////
139 private string GetDbPath (string directory)
141 return Path.Combine (directory, "FileAttributesStore.db");
144 private SqliteConnection Open (string directory)
146 SqliteConnection c;
147 c = new SqliteConnection ();
148 c.ConnectionString = "URI=file:" + GetDbPath (directory);
149 c.Open ();
150 return c;
153 private void DoNonQuery (string format, params object [] args)
155 SqliteCommand command;
156 command = new SqliteCommand ();
157 command.Connection = connection;
158 command.CommandText = String.Format (format, args);
159 command.ExecuteNonQuery ();
160 command.Dispose ();
163 private SqliteCommand QueryCommand (string where_format, params object [] where_args)
165 SqliteCommand command;
166 command = new SqliteCommand ();
167 command.Connection = connection;
168 command.CommandText =
169 "SELECT unique_id, directory, filename, last_mtime, last_indexed, filter_name, filter_version " +
170 "FROM file_attributes WHERE " +
171 String.Format (where_format, where_args);
172 return command;
175 private FileAttributes GetFromReader (SqliteDataReader reader)
177 FileAttributes attr = new FileAttributes ();
179 attr.UniqueId = GuidFu.FromShortString (reader.GetString (0));
180 attr.Path = System.IO.Path.Combine (reader.GetString (1), reader.GetString (2));
181 attr.LastWriteTime = StringFu.StringToDateTime (reader.GetString (3));
182 attr.LastIndexedTime = StringFu.StringToDateTime (reader.GetString (4));
183 attr.FilterName = reader.GetString (5);
184 attr.FilterVersion = int.Parse (reader.GetString (6));
186 return attr;
189 private SqliteCommand PathQueryCommand ()
191 SqliteCommand command;
192 command = new SqliteCommand ();
193 command.Connection = connection;
194 command.CommandText = "SELECT directory, filename FROM file_attributes";
195 return command;
198 ///////////////////////////////////////////////////////////////////
200 private int GetPathHash (string path)
202 uint hash = 0xdeadbeef;
203 foreach (char c in path)
204 hash = 17 * hash + (uint) c;
205 // Fold the 32 bits in 16.
206 return (int) ((hash & 0xffff) ^ (hash >> 16));
209 private bool GetPathFlag (string path)
211 int hash = GetPathHash (path);
212 int index = hash >> 3;
213 byte mask = (byte) (1 << (hash & 0x7));
214 return (path_flags [index] & mask) != 0;
217 private void SetPathFlag (string path, bool value)
219 int hash = GetPathHash (path);
220 int index = hash >> 3;
221 byte mask = (byte) (1 << (hash & 0x7));
223 if (value)
224 path_flags [index] |= mask;
225 else
226 path_flags [index] &= (byte) ~mask;
229 ///////////////////////////////////////////////////////////////////
231 public FileAttributes Read (string path)
233 SqliteCommand command;
234 SqliteDataReader reader;
236 if (! GetPathFlag (path))
237 return null;
239 FileAttributes attr = null;
240 bool found_too_many = false;
242 // We need to quote any 's that appear in the strings
243 // (int particular, in the path)
244 string directory = Path.GetDirectoryName (path).Replace ("'", "''");
245 string filename = Path.GetFileName (path).Replace ("'", "''");
246 lock (connection) {
247 command = QueryCommand ("directory='{0}' AND filename='{1}'",
248 directory, filename);
249 reader = command.ExecuteReader ();
251 if (reader.Read ()) {
252 attr = GetFromReader (reader);
254 if (reader.Read ())
255 found_too_many = true;
257 reader.Close ();
258 command.Dispose ();
260 // If we found more than one matching record for a given
261 // directory and filename, something has gone wrong.
262 // Since we have no way of knowing which one is correct
263 // and which isn't, we delete them all and return
264 // null. (Which in most cases will force a re-index.
265 if (found_too_many) {
266 DoNonQuery ("DELETE FROM file_attributes WHERE directory='{0}' AND filename='{1}'",
267 directory, filename);
271 return attr;
274 public bool Write (FileAttributes fa)
276 SetPathFlag (fa.Path, true);
278 // We need to quote any 's that appear in the strings
279 // (in particular, in the path)
280 lock (connection) {
281 DoNonQuery ("INSERT OR REPLACE INTO file_attributes " +
282 " (unique_id, directory, filename, last_mtime, last_indexed, filter_name, filter_version) " +
283 " VALUES ('{0}', '{1}', '{2}', '{3}', '{4}', '{5}', '{6}')",
284 GuidFu.ToShortString (fa.UniqueId),
285 fa.Directory.Replace ("'", "''"), fa.Filename.Replace ("'", "''"),
286 StringFu.DateTimeToString (fa.LastWriteTime),
287 StringFu.DateTimeToString (fa.LastIndexedTime),
288 fa.FilterName,
289 fa.FilterVersion);
291 return true;
294 public void Drop (string path)
296 // We don't want to SetPathFlag (path, false) here, since we have no way of knowing
297 // if another path hashes to the same value as this one.
299 // We need to quote any 's that appear in the strings
300 // (in particular, in the path)
301 string directory = Path.GetDirectoryName (path).Replace ("'", "''");
302 string filename = Path.GetFileName (path).Replace ("'", "''");
303 lock (connection) {
304 DoNonQuery ("DELETE FROM file_attributes WHERE directory='{0}' AND filename='{1}'",
305 directory, filename);