* tools/Info.cs: Add --list-backends, --list-static-indexes to
[beagle.git] / beagled / TextCache.cs
blobc9a8b5dd79b9eb47130ec6958eab1894744697c8
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;
35 using Beagle.Util;
37 namespace Beagle.Daemon {
39 public class TextCache {
41 static public bool Debug = false;
43 public const string SELF_CACHE_TAG = "*self*";
45 private string text_cache_dir;
46 private SqliteConnection connection;
48 private enum TransactionState {
49 None,
50 Requested,
51 Started
53 private TransactionState transaction_state;
55 private static TextCache user_cache = null;
57 public static TextCache UserCache {
58 get {
59 if (user_cache == null)
60 user_cache = new TextCache (PathFinder.StorageDir);
62 return user_cache;
66 public TextCache (string storage_dir) : this (storage_dir, false) { }
68 public TextCache (string storage_dir, bool read_only)
70 text_cache_dir = Path.Combine (storage_dir, "TextCache");
71 if (! Directory.Exists (text_cache_dir)) {
72 Directory.CreateDirectory (text_cache_dir);
74 // Create our cache subdirectories.
75 for (int i = 0; i < 256; ++i) {
76 string subdir = i.ToString ("x");
77 if (i < 16)
78 subdir = "0" + subdir;
79 subdir = Path.Combine (text_cache_dir, subdir);
80 Directory.CreateDirectory (subdir);
84 // Create our Sqlite database
85 string db_filename = Path.Combine (text_cache_dir, "TextCache.db");
86 bool create_new_db = false;
87 if (! File.Exists (db_filename))
88 create_new_db = true;
90 // Funky logic here to deal with sqlite versions.
92 // When sqlite 3 tries to open an sqlite 2 database,
93 // it will throw an SqliteException with SqliteError
94 // NOTADB when trying to execute a command.
96 // When sqlite 2 tries to open an sqlite 3 database,
97 // it will throw an ApplicationException when it
98 // tries to open the database.
100 try {
101 connection = Open (db_filename);
102 } catch (ApplicationException) {
103 Logger.Log.Warn ("Likely sqlite database version mismatch trying to open {0}. Purging.", db_filename);
104 create_new_db = true;
107 if (!create_new_db) {
108 // Run a dummy query to see if we get a NOTADB error. Sigh.
109 SqliteCommand command;
110 SqliteDataReader reader = null;
112 command = new SqliteCommand ();
113 command.Connection = connection;
114 command.CommandText =
115 "SELECT filename FROM uri_index WHERE uri='blah'";
117 try {
118 reader = ExecuteReaderOrWait (command);
119 } catch (ApplicationException ex) {
120 Logger.Log.Warn ("Likely sqlite database version mismatch trying to read from {0}. Purging.", db_filename);
121 create_new_db = true;
124 if (reader != null)
125 reader.Dispose ();
126 command.Dispose ();
129 if (create_new_db) {
130 if (connection != null)
131 connection.Dispose ();
133 if (read_only)
134 throw new UnauthorizedAccessException (String.Format ("Unable to create read only text cache {0}", db_filename));
136 File.Delete (db_filename);
137 connection = Open (db_filename);
139 DoNonQuery ("CREATE TABLE uri_index ( " +
140 " uri STRING UNIQUE NOT NULL, " +
141 " filename STRING NOT NULL " +
142 ")");
146 private SqliteConnection Open (string db_filename)
148 SqliteConnection connection = new SqliteConnection ();
149 connection.ConnectionString = "version=" + ExternalStringsHack.SqliteVersion
150 + ",encoding=UTF-8,URI=file:" + db_filename;
151 connection.Open ();
152 return connection;
155 private static string UriToString (Uri uri)
157 return UriFu.UriToSerializableString (uri).Replace ("'", "''");
160 private SqliteCommand NewCommand (string format, params object [] args)
162 SqliteCommand command;
163 command = new SqliteCommand ();
164 command.Connection = connection;
165 command.CommandText = String.Format (format, args);
166 return command;
169 private void DoNonQuery (string format, params object [] args)
171 if (Debug)
172 Logger.Log.Debug ("Executing command '{0}'", String.Format (format, args));
173 SqliteCommand command = NewCommand (format, args);
174 while (true) {
175 try {
176 command.ExecuteNonQuery ();
177 break;
178 } catch (SqliteBusyException ex) {
179 // FIXME: should we eventually time out?
180 Thread.Sleep (50);
183 command.Dispose ();
186 private SqliteDataReader ExecuteReaderOrWait (SqliteCommand command)
188 SqliteDataReader reader = null;
189 while (reader == null) {
190 try {
191 reader = command.ExecuteReader ();
192 } catch (SqliteBusyException ex) {
193 Thread.Sleep (50);
196 return reader;
199 private bool ReadOrWait (SqliteDataReader reader)
201 while (true) {
202 try {
203 return reader.Read ();
204 } catch (SqliteBusyException ex) {
205 Thread.Sleep (50);
210 private void Insert (Uri uri, string filename)
212 lock (connection) {
213 MaybeStartTransaction_Unlocked ();
214 DoNonQuery ("INSERT OR REPLACE INTO uri_index (uri, filename) VALUES ('{0}', '{1}')",
215 UriToString (uri), filename);
219 private string LookupPathRawUnlocked (Uri uri, bool create_if_not_found)
221 SqliteCommand command;
222 SqliteDataReader reader = null;
223 string path = null;
225 command = NewCommand ("SELECT filename FROM uri_index WHERE uri='{0}'",
226 UriToString (uri));
227 reader = ExecuteReaderOrWait (command);
228 if (ReadOrWait (reader))
229 path = reader.GetString (0);
230 reader.Close ();
231 command.Dispose ();
233 if (path == null && create_if_not_found) {
234 string guid = Guid.NewGuid ().ToString ();
235 path = Path.Combine (guid.Substring (0, 2), guid.Substring (2));
236 Insert (uri, path);
239 if (path == SELF_CACHE_TAG)
240 return SELF_CACHE_TAG;
242 return path != null ? Path.Combine (text_cache_dir, path) : null;
245 // Don't do this unless you know what you are doing! If you
246 // do anything to the path you get back other than open and
247 // read the file, you will almost certainly break something.
248 // And it will be evidence that you are a bad person and that
249 // you deserve whatever horrible fate befalls you.
250 public string LookupPathRaw (Uri uri)
252 lock (connection)
253 return LookupPathRawUnlocked (uri, false);
256 private string LookupPath (Uri uri, bool create_if_not_found)
258 lock (connection) {
259 string path = LookupPathRawUnlocked (uri, create_if_not_found);
260 if (path == SELF_CACHE_TAG) {
261 // FIXME: How do we handle URI remapping for self-cached items?
262 #if false
263 if (uri_remapper != null)
264 uri = uri_remapper (uri);
265 #endif
266 if (! uri.IsFile) {
267 string msg = String.Format ("Non-file uri {0} flagged as self-cached", uri);
268 throw new Exception (msg);
270 return uri.LocalPath;
272 return path;
276 public void MarkAsSelfCached (Uri uri)
278 lock (connection)
279 Insert (uri, SELF_CACHE_TAG);
282 private bool world_readable = false;
283 public bool WorldReadable {
284 get { return this.world_readable; }
285 set { this.world_readable = value; }
288 public TextWriter GetWriter (Uri uri)
290 // FIXME: Uri remapping?
291 string path = LookupPath (uri, true);
293 FileStream stream;
294 stream = new FileStream (path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
296 // We don't expect to need this again in the near future.
297 FileAdvise.FlushCache (stream);
299 if (! world_readable) {
300 // Make files only readable by the owner.
301 Mono.Unix.Native.Syscall.chmod (path, (Mono.Unix.Native.FilePermissions) 384);
304 StreamWriter writer;
305 writer = new StreamWriter (stream);
306 return writer;
309 public void WriteFromReader (Uri uri, TextReader reader)
311 TextWriter writer;
312 writer = GetWriter (uri);
313 string line;
314 while ((line = reader.ReadLine ()) != null)
315 writer.WriteLine (line);
316 writer.Close ();
319 public void WriteFromString (Uri uri, string str)
321 if (str == null) {
322 Delete (uri);
323 return;
326 TextWriter writer;
327 writer = GetWriter (uri);
328 writer.WriteLine (str);
329 writer.Close ();
332 // FIXME: Uri remapping?
333 public TextReader GetReader (Uri uri)
335 string path = LookupPath (uri, false);
336 if (path == null)
337 return null;
339 FileStream stream;
340 try {
341 stream = new FileStream (path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
342 } catch (FileNotFoundException ex) {
343 return null;
346 StreamReader reader;
347 reader = new StreamReader (stream);
348 return reader;
351 public void Delete (Uri uri)
353 lock (connection) {
354 string path = LookupPathRawUnlocked (uri, false);
355 if (path != null) {
356 MaybeStartTransaction_Unlocked ();
357 DoNonQuery ("DELETE FROM uri_index WHERE uri='{0}' AND filename='{1}'",
358 UriToString (uri), path);
359 if (path != SELF_CACHE_TAG)
360 File.Delete (path);
365 private void MaybeStartTransaction_Unlocked ()
367 if (transaction_state == TransactionState.Requested)
368 DoNonQuery ("BEGIN");
369 transaction_state = TransactionState.Started;
372 public void BeginTransaction ()
374 lock (connection) {
375 if (transaction_state == TransactionState.None)
376 transaction_state = TransactionState.Requested;
380 public void CommitTransaction ()
382 lock (connection) {
383 if (transaction_state == TransactionState.Started)
384 DoNonQuery ("COMMIT");
385 transaction_state = TransactionState.None;