Thumbnail file hits. Based on a patch from D Bera
[beagle.git] / beagled / TextCache.cs
blobbe9dfef13a7037b06ae3433425fa74c52e704127
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 public const string SELF_CACHE_TAG = "*self*";
43 private string text_cache_dir;
44 private SqliteConnection connection;
46 private enum TransactionState {
47 None,
48 Requested,
49 Started
51 private TransactionState transaction_state;
53 private static TextCache user_cache = null;
55 public static TextCache UserCache {
56 get {
57 if (user_cache == null)
58 user_cache = new TextCache (PathFinder.StorageDir);
60 return user_cache;
64 public TextCache (string storage_dir)
66 text_cache_dir = Path.Combine (storage_dir, "TextCache");
67 if (! Directory.Exists (text_cache_dir)) {
68 Directory.CreateDirectory (text_cache_dir);
70 // Create our cache subdirectories.
71 for (int i = 0; i < 256; ++i) {
72 string subdir = i.ToString ("x");
73 if (i < 16)
74 subdir = "0" + subdir;
75 subdir = Path.Combine (text_cache_dir, subdir);
76 Directory.CreateDirectory (subdir);
80 // Create our Sqlite database
81 string db_filename = Path.Combine (text_cache_dir, "TextCache.db");
82 bool create_new_db = false;
83 if (! File.Exists (db_filename))
84 create_new_db = true;
86 connection = new SqliteConnection ();
87 connection.ConnectionString = "URI=file:" + db_filename;
88 connection.Open ();
90 if (create_new_db) {
91 DoNonQuery ("CREATE TABLE uri_index ( " +
92 " uri STRING UNIQUE NOT NULL, " +
93 " filename STRING NOT NULL " +
94 ")");
98 private static string UriToString (Uri uri)
100 return uri.ToString ().Replace ("'", "''");
103 private SqliteCommand NewCommand (string format, params object [] args)
105 SqliteCommand command;
106 command = new SqliteCommand ();
107 command.Connection = connection;
108 command.CommandText = String.Format (format, args);
109 return command;
112 private void DoNonQuery (string format, params object [] args)
114 SqliteCommand command = NewCommand (format, args);
115 while (true) {
116 try {
117 command.ExecuteNonQuery ();
118 break;
119 } catch (SqliteException ex) {
120 // FIXME: should we eventually time out?
121 if (ex.SqliteError == SqliteError.BUSY)
122 Thread.Sleep (50);
123 else
124 throw ex;
127 command.Dispose ();
130 private SqliteDataReader ExecuteReaderOrWait (SqliteCommand command)
132 SqliteDataReader reader = null;
133 while (reader == null) {
134 try {
135 reader = command.ExecuteReader ();
136 } catch (SqliteException ex) {
137 if (ex.SqliteError == SqliteError.BUSY)
138 Thread.Sleep (50);
139 else
140 throw ex;
143 return reader;
146 private bool ReadOrWait (SqliteDataReader reader)
148 while (true) {
149 try {
150 return reader.Read ();
151 } catch (SqliteException ex) {
152 if (ex.SqliteError == SqliteError.BUSY)
153 Thread.Sleep (50);
154 else
155 throw ex;
160 private void Insert (Uri uri, string filename)
162 MaybeStartTransaction ();
163 DoNonQuery ("INSERT OR REPLACE INTO uri_index (uri, filename) VALUES ('{0}', '{1}')",
164 UriToString (uri), filename);
167 private string LookupPathRawUnlocked (Uri uri, bool create_if_not_found)
169 SqliteCommand command;
170 SqliteDataReader reader = null;
171 string path = null;
173 command = NewCommand ("SELECT filename FROM uri_index WHERE uri='{0}'",
174 UriToString (uri));
175 reader = ExecuteReaderOrWait (command);
176 if (ReadOrWait (reader))
177 path = reader.GetString (0);
178 reader.Close ();
179 command.Dispose ();
181 if (path == null && create_if_not_found) {
182 string guid = Guid.NewGuid ().ToString ();
183 path = Path.Combine (guid.Substring (0, 2), guid.Substring (2));
184 Insert (uri, path);
187 if (path == SELF_CACHE_TAG)
188 return SELF_CACHE_TAG;
190 return path != null ? Path.Combine (text_cache_dir, path) : null;
193 // Don't do this unless you know what you are doing! If you
194 // do anything to the path you get back other than open and
195 // read the file, you will almost certainly break something.
196 // And it will be evidence that you are a bad person and that
197 // you deserve whatever horrible fate befalls you.
198 public string LookupPathRaw (Uri uri)
200 lock (connection)
201 return LookupPathRawUnlocked (uri, false);
204 private string LookupPath (Uri uri, bool create_if_not_found)
206 lock (connection) {
207 string path = LookupPathRawUnlocked (uri, create_if_not_found);
208 if (path == SELF_CACHE_TAG) {
209 // FIXME: How do we handle URI remapping for self-cached items?
210 #if false
211 if (uri_remapper != null)
212 uri = uri_remapper (uri);
213 #endif
214 if (! uri.IsFile) {
215 string msg = String.Format ("Non-file uri {0} flagged as self-cached", uri);
216 throw new Exception (msg);
218 return uri.LocalPath;
220 return path;
224 public void MarkAsSelfCached (Uri uri)
226 lock (connection)
227 Insert (uri, SELF_CACHE_TAG);
230 public TextWriter GetWriter (Uri uri)
232 // FIXME: Uri remapping?
233 string path = LookupPath (uri, true);
235 FileStream stream;
236 stream = new FileStream (path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
238 // We don't expect to need this again in the near future.
239 FileAdvise.FlushCache (stream);
241 StreamWriter writer;
242 writer = new StreamWriter (stream);
243 return writer;
246 public void WriteFromReader (Uri uri, TextReader reader)
248 TextWriter writer;
249 writer = GetWriter (uri);
250 string line;
251 while ((line = reader.ReadLine ()) != null)
252 writer.WriteLine (line);
253 writer.Close ();
256 public void WriteFromString (Uri uri, string str)
258 if (str == null) {
259 Delete (uri);
260 return;
263 TextWriter writer;
264 writer = GetWriter (uri);
265 writer.WriteLine (str);
266 writer.Close ();
269 // FIXME: Uri remapping?
270 public TextReader GetReader (Uri uri)
272 string path = LookupPath (uri, false);
273 if (path == null)
274 return null;
276 FileStream stream;
277 try {
278 stream = new FileStream (path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
279 } catch (FileNotFoundException ex) {
280 return null;
283 StreamReader reader;
284 reader = new StreamReader (stream);
285 return reader;
288 public void Delete (Uri uri)
290 lock (connection) {
291 string path = LookupPathRawUnlocked (uri, false);
292 if (path != null) {
293 MaybeStartTransaction ();
294 DoNonQuery ("DELETE FROM uri_index WHERE uri='{0}' AND filename='{1}'",
295 UriToString (uri), path);
296 if (path != SELF_CACHE_TAG)
297 File.Delete (path);
302 private void MaybeStartTransaction ()
304 if (transaction_state == TransactionState.Requested) {
305 DoNonQuery ("BEGIN");
306 transaction_state = TransactionState.Started;
310 public void BeginTransaction ()
312 if (transaction_state == TransactionState.None)
313 transaction_state = TransactionState.Requested;
316 public void CommitTransaction ()
318 if (transaction_state == TransactionState.Started) {
319 lock (connection)
320 DoNonQuery ("COMMIT");
322 transaction_state = TransactionState.None;