4 // Copyright (C) 2004 Novell, Inc.
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.
29 using System
.Collections
;
31 using System
.Threading
;
33 using Mono
.Data
.SqliteClient
;
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
{
51 private TransactionState transaction_state
;
53 private static TextCache user_cache
= null;
55 public static TextCache UserCache
{
57 if (user_cache
== null)
58 user_cache
= new TextCache (PathFinder
.StorageDir
);
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");
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
))
86 connection
= new SqliteConnection ();
87 connection
.ConnectionString
= "URI=file:" + db_filename
;
91 DoNonQuery ("CREATE TABLE uri_index ( " +
92 " uri STRING UNIQUE NOT NULL, " +
93 " filename STRING NOT NULL " +
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
);
112 private void DoNonQuery (string format
, params object [] args
)
114 SqliteCommand command
= NewCommand (format
, args
);
117 command
.ExecuteNonQuery ();
119 } catch (SqliteException ex
) {
120 // FIXME: should we eventually time out?
121 if (ex
.SqliteError
== SqliteError
.BUSY
)
130 private SqliteDataReader
ExecuteReaderOrWait (SqliteCommand command
)
132 SqliteDataReader reader
= null;
133 while (reader
== null) {
135 reader
= command
.ExecuteReader ();
136 } catch (SqliteException ex
) {
137 if (ex
.SqliteError
== SqliteError
.BUSY
)
146 private bool ReadOrWait (SqliteDataReader reader
)
150 return reader
.Read ();
151 } catch (SqliteException ex
) {
152 if (ex
.SqliteError
== SqliteError
.BUSY
)
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;
173 command
= NewCommand ("SELECT filename FROM uri_index WHERE uri='{0}'",
175 reader
= ExecuteReaderOrWait (command
);
176 if (ReadOrWait (reader
))
177 path
= reader
.GetString (0);
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));
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
)
201 return LookupPathRawUnlocked (uri
, false);
204 private string LookupPath (Uri uri
, bool create_if_not_found
)
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?
211 if (uri_remapper
!= null)
212 uri
= uri_remapper (uri
);
215 string msg
= String
.Format ("Non-file uri {0} flagged as self-cached", uri
);
216 throw new Exception (msg
);
218 return uri
.LocalPath
;
224 public void MarkAsSelfCached (Uri uri
)
227 Insert (uri
, SELF_CACHE_TAG
);
230 public TextWriter
GetWriter (Uri uri
)
232 // FIXME: Uri remapping?
233 string path
= LookupPath (uri
, true);
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
);
242 writer
= new StreamWriter (stream
);
246 public void WriteFromReader (Uri uri
, TextReader reader
)
249 writer
= GetWriter (uri
);
251 while ((line
= reader
.ReadLine ()) != null)
252 writer
.WriteLine (line
);
256 public void WriteFromString (Uri uri
, string str
)
264 writer
= GetWriter (uri
);
265 writer
.WriteLine (str
);
269 // FIXME: Uri remapping?
270 public TextReader
GetReader (Uri uri
)
272 string path
= LookupPath (uri
, false);
278 stream
= new FileStream (path
, FileMode
.Open
, FileAccess
.Read
, FileShare
.ReadWrite
);
279 } catch (FileNotFoundException ex
) {
284 reader
= new StreamReader (stream
);
288 public void Delete (Uri uri
)
291 string path
= LookupPathRawUnlocked (uri
, false);
293 MaybeStartTransaction ();
294 DoNonQuery ("DELETE FROM uri_index WHERE uri='{0}' AND filename='{1}'",
295 UriToString (uri
), path
);
296 if (path
!= SELF_CACHE_TAG
)
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
) {
320 DoNonQuery ("COMMIT");
322 transaction_state
= TransactionState
.None
;