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
;
31 using Mono
.Data
.SqliteClient
;
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
))) {
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
;
64 "SELECT version, fingerprint FROM db_info";
66 reader
= command
.ExecuteReader ();
67 } catch (Exception ex
) {
70 if (reader
!= null && ! create_new_db
) {
72 stored_version
= reader
.GetInt32 (0);
73 stored_fingerprint
= reader
.GetString (1);
79 if (VERSION
!= stored_version
80 || (index_fingerprint
!= null && index_fingerprint
!= stored_fingerprint
))
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 " +
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 " +
108 SqliteCommand command
;
109 SqliteDataReader reader
;
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);
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
)
147 c
= new SqliteConnection ();
148 c
.ConnectionString
= "URI=file:" + GetDbPath (directory
);
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 ();
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
);
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));
189 private SqliteCommand
PathQueryCommand ()
191 SqliteCommand command
;
192 command
= new SqliteCommand ();
193 command
.Connection
= connection
;
194 command
.CommandText
= "SELECT directory, filename FROM file_attributes";
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));
224 path_flags
[index
] |= mask
;
226 path_flags
[index
] &= (byte) ~mask
;
229 ///////////////////////////////////////////////////////////////////
231 public FileAttributes
Read (string path
)
233 SqliteCommand command
;
234 SqliteDataReader reader
;
236 if (! GetPathFlag (path
))
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 ("'", "''");
247 command
= QueryCommand ("directory='{0}' AND filename='{1}'",
248 directory
, filename
);
249 reader
= command
.ExecuteReader ();
251 if (reader
.Read ()) {
252 attr
= GetFromReader (reader
);
255 found_too_many
= true;
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
);
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)
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
),
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 ("'", "''");
304 DoNonQuery ("DELETE FROM file_attributes WHERE directory='{0}' AND filename='{1}'",
305 directory
, filename
);