QueryResponses.cs, DumpIndex.cs, IQueryResult.cs, QueryExecutor.cs, QueryResult.cs...
[beagle.git] / beagled / TextCache.cs
blob6c5d74136bf2b85514e8d7c781441cb758721af9
1 //
2 // TextCache.cs
3 //
4 // Copyright (C) 2004 Novell, Inc.
5 //
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all 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
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
28 using System;
29 using System.Collections;
30 using System.IO;
31 using System.Threading;
33 using Mono.Data.SqliteClient;
34 using ICSharpCode.SharpZipLib.GZip;
36 using Beagle.Util;
38 namespace Beagle.Daemon {
40 public class TextCache {
42 static public bool Debug = false;
44 public const string SELF_CACHE_TAG = "*self*";
46 private string text_cache_dir;
47 private SqliteConnection connection;
49 private enum TransactionState {
50 None,
51 Requested,
52 Started
54 private TransactionState transaction_state;
56 private static TextCache user_cache = null;
58 public static TextCache UserCache {
59 get {
60 if (user_cache == null)
61 user_cache = new TextCache (PathFinder.StorageDir);
63 return user_cache;
67 public TextCache (string storage_dir) : this (storage_dir, false) { }
69 public TextCache (string storage_dir, bool read_only)
71 text_cache_dir = Path.Combine (storage_dir, "TextCache");
72 if (! Directory.Exists (text_cache_dir)) {
73 Directory.CreateDirectory (text_cache_dir);
75 // Create our cache subdirectories.
76 for (int i = 0; i < 256; ++i) {
77 string subdir = i.ToString ("x");
78 if (i < 16)
79 subdir = "0" + subdir;
80 subdir = Path.Combine (text_cache_dir, subdir);
81 Directory.CreateDirectory (subdir);
85 // Create our Sqlite database
86 string db_filename = Path.Combine (text_cache_dir, "TextCache.db");
87 bool create_new_db = false;
88 if (! File.Exists (db_filename))
89 create_new_db = true;
91 // Funky logic here to deal with sqlite versions.
93 // When sqlite 3 tries to open an sqlite 2 database,
94 // it will throw an SqliteException with SqliteError
95 // NOTADB when trying to execute a command.
97 // When sqlite 2 tries to open an sqlite 3 database,
98 // it will throw an ApplicationException when it
99 // tries to open the database.
101 try {
102 connection = Open (db_filename);
103 } catch (ApplicationException) {
104 Logger.Log.Warn ("Likely sqlite database version mismatch trying to open {0}. Purging.", db_filename);
105 create_new_db = true;
108 if (!create_new_db) {
109 // Run a dummy query to see if we get a NOTADB error. Sigh.
110 SqliteCommand command;
111 SqliteDataReader reader = null;
113 command = new SqliteCommand ();
114 command.Connection = connection;
115 command.CommandText =
116 "SELECT filename FROM uri_index WHERE uri='blah'";
118 try {
119 reader = SqliteUtils.ExecuteReaderOrWait (command);
120 } catch (ApplicationException ex) {
121 Logger.Log.Warn ("Likely sqlite database version mismatch trying to read from {0}. Purging.", db_filename);
122 create_new_db = true;
125 if (reader != null)
126 reader.Dispose ();
127 command.Dispose ();
130 if (create_new_db) {
131 if (connection != null)
132 connection.Dispose ();
134 if (read_only)
135 throw new UnauthorizedAccessException (String.Format ("Unable to create read only text cache {0}", db_filename));
137 File.Delete (db_filename);
139 try {
140 connection = Open (db_filename);
141 } catch (Exception e) {
142 Log.Debug (e, "Exception opening text cache {0}", db_filename);
145 SqliteUtils.DoNonQuery (connection,
146 "CREATE TABLE uri_index ( " +
147 " uri STRING UNIQUE NOT NULL, " +
148 " filename STRING NOT NULL " +
149 ")");
153 private SqliteConnection Open (string db_filename)
155 SqliteConnection connection = new SqliteConnection ();
156 connection.ConnectionString = "version=" + ExternalStringsHack.SqliteVersion
157 + ",encoding=UTF-8,URI=file:" + db_filename;
158 connection.Open ();
159 return connection;
162 private static string UriToString (Uri uri)
164 return UriFu.UriToEscapedString (uri).Replace ("'", "''");
167 private SqliteCommand NewCommand (string format, params object [] args)
169 SqliteCommand command;
170 command = new SqliteCommand ();
171 command.Connection = connection;
172 command.CommandText = String.Format (format, args);
173 return command;
176 private void Insert (Uri uri, string filename)
178 lock (connection) {
179 MaybeStartTransaction_Unlocked ();
180 SqliteUtils.DoNonQuery (connection,
181 "INSERT OR REPLACE INTO uri_index (uri, filename) VALUES ('{0}', '{1}')",
182 UriToString (uri), filename);
186 private string LookupPathRawUnlocked (Uri uri, bool create_if_not_found)
188 SqliteCommand command;
189 SqliteDataReader reader = null;
190 string path = null;
192 command = NewCommand ("SELECT filename FROM uri_index WHERE uri='{0}'",
193 UriToString (uri));
194 reader = SqliteUtils.ExecuteReaderOrWait (command);
195 if (SqliteUtils.ReadOrWait (reader))
196 path = reader.GetString (0);
197 reader.Close ();
198 command.Dispose ();
200 if (path == null && create_if_not_found) {
201 string guid = Guid.NewGuid ().ToString ();
202 path = Path.Combine (guid.Substring (0, 2), guid.Substring (2));
203 Insert (uri, path);
206 if (path == SELF_CACHE_TAG)
207 return SELF_CACHE_TAG;
209 return path != null ? Path.Combine (text_cache_dir, path) : null;
212 // Don't do this unless you know what you are doing! If you
213 // do anything to the path you get back other than open and
214 // read the file, you will almost certainly break something.
215 // And it will be evidence that you are a bad person and that
216 // you deserve whatever horrible fate befalls you.
217 public string LookupPathRaw (Uri uri)
219 lock (connection)
220 return LookupPathRawUnlocked (uri, false);
223 private string LookupPath (Uri uri, bool create_if_not_found)
225 lock (connection) {
226 string path = LookupPathRawUnlocked (uri, create_if_not_found);
227 if (path == SELF_CACHE_TAG) {
228 // FIXME: How do we handle URI remapping for self-cached items?
229 #if false
230 if (uri_remapper != null)
231 uri = uri_remapper (uri);
232 #endif
233 if (! uri.IsFile) {
234 string msg = String.Format ("Non-file uri {0} flagged as self-cached", uri);
235 throw new Exception (msg);
237 return uri.LocalPath;
239 return path;
243 public void MarkAsSelfCached (Uri uri)
245 lock (connection)
246 Insert (uri, SELF_CACHE_TAG);
249 private bool world_readable = false;
250 public bool WorldReadable {
251 get { return this.world_readable; }
252 set { this.world_readable = value; }
255 public TextWriter GetWriter (Uri uri)
257 // FIXME: Uri remapping?
258 string path = LookupPath (uri, true);
260 FileStream fs;
261 fs = new FileStream (path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
263 // We don't expect to need this again in the near future.
264 FileAdvise.FlushCache (fs);
266 GZipOutputStream stream;
267 stream = new GZipOutputStream (fs);
269 if (! world_readable) {
270 // Make files only readable by the owner.
271 Mono.Unix.Native.Syscall.chmod (path, (Mono.Unix.Native.FilePermissions) 384);
274 StreamWriter writer;
275 writer = new StreamWriter (stream);
276 return writer;
279 public void WriteFromReader (Uri uri, TextReader reader)
281 TextWriter writer;
282 writer = GetWriter (uri);
283 string line;
284 while ((line = reader.ReadLine ()) != null)
285 writer.WriteLine (line);
286 writer.Close ();
289 public void WriteFromString (Uri uri, string str)
291 if (str == null) {
292 Delete (uri);
293 return;
296 TextWriter writer;
297 writer = GetWriter (uri);
298 writer.WriteLine (str);
299 writer.Close ();
302 // FIXME: Uri remapping?
303 public TextReader GetReader (Uri uri)
305 string path = LookupPath (uri, false);
306 if (path == null)
307 return null;
309 return GetReader (path);
312 public TextReader GetReader (string path)
314 FileStream file_stream;
315 try {
316 file_stream = new FileStream (path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
317 } catch (FileNotFoundException ex) {
318 return null;
321 StreamReader reader = null;
322 try {
323 Stream stream = new GZipInputStream (file_stream);
324 reader = new StreamReader (stream);
326 // This throws an exception if the file isn't compressed as follows:
327 // 1.) IOException on older versions of SharpZipLib
328 // 2.) GZipException on newer versions of SharpZipLib
329 // FIXME: Change this to GZipException when we depend
330 // on a higer version of SharpZipLib
331 reader.Peek ();
332 } catch (Exception ex) {
333 reader = new StreamReader (file_stream);
336 return reader;
339 public void Delete (Uri uri)
341 lock (connection) {
342 string path = LookupPathRawUnlocked (uri, false);
343 if (path != null) {
344 MaybeStartTransaction_Unlocked ();
345 SqliteUtils.DoNonQuery (connection,
346 "DELETE FROM uri_index WHERE uri='{0}' AND filename='{1}'",
347 UriToString (uri), path);
348 if (path != SELF_CACHE_TAG)
349 File.Delete (path);
354 private void MaybeStartTransaction_Unlocked ()
356 if (transaction_state == TransactionState.Requested)
357 SqliteUtils.DoNonQuery (connection, "BEGIN");
358 transaction_state = TransactionState.Started;
361 public void BeginTransaction ()
363 lock (connection) {
364 if (transaction_state == TransactionState.None)
365 transaction_state = TransactionState.Requested;
369 public void CommitTransaction ()
371 lock (connection) {
372 if (transaction_state == TransactionState.Started)
373 SqliteUtils.DoNonQuery (connection, "COMMIT");
374 transaction_state = TransactionState.None;