1
/****************************************************************************
3
| Copyright (c) 2007 Novell, Inc.
6
| This program is free software; you can redistribute it and/or
7
| modify it under the terms of version 2 of the GNU General Public License as
8
| published by the Free Software Foundation.
10
| This program is distributed in the hope that it will be useful,
11
| but WITHOUT ANY WARRANTY; without even the implied warranty of
12
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
| GNU General Public License for more details.
15
| You should have received a copy of the GNU General Public License
16
| along with this program; if not, contact Novell, Inc.
18
| To contact Novell about this file by physical or electronic mail,
19
| you may find current contact information at www.novell.com
22
|***************************************************************************/
26
using System.Text.RegularExpressions;
28
using System.Diagnostics;
29
using System.Collections;
30
using System.Security.Policy;
31
using System.Threading;
32
using Simias.Storage.Provider;
33
using System.Runtime.InteropServices;
34
using System.Runtime.Remoting;
35
using System.Runtime.Remoting.Activation;
36
using System.Runtime.Remoting.Channels;
37
using System.Runtime.Remoting.Channels.Tcp;
38
using System.Runtime.Serialization.Formatters.Binary;
41
namespace Simias.Storage.Provider.Fs
47
/// Queue used to keep track of Fs record IDs.
48
/// When a node gets deleted the id is put on the queue
49
/// so that a new object can reuse the ID.
51
static Hashtable dbTable = new Hashtable();
53
public IntPtr pFWDB = IntPtr.Zero;
55
bool deleteDb = false;
62
public static FsDb GetFsDb(string name)
67
FsDb = (FsDb)dbTable[name];
70
FsDb = new FsDb(name);
71
dbTable.Add(name, FsDb);
84
dbTable.Remove(dbName);
87
FsProvider.DeleteStore(dbName);
95
set {deleteDb = value;}
101
/// Class that implements the Simias.Storage.Provider.IProvider interface
102
/// using the native file system.
104
public class FsProvider : MarshalByRefObject, IProvider
108
bool AlreadyDisposed;
111
const string DbName = "Collection.db";
112
const string version = "0.1";
116
#region Constructor/Destructor
120
/// <param name="conf">The configuration object used to configure this instance.</param>
121
public FsProvider(ProviderConfig conf)
124
storePath = System.IO.Path.GetFullPath(conf.Path);
125
DbPath = Path.Combine(storePath, DbName);
126
FsDb = FsDb.GetFsDb(DbPath);
138
#region Transaction class
145
bool deleteContainer = false;
147
internal Transaction(string dbPath, string container)
149
path = Path.Combine(dbPath, container);
150
addPath = Path.Combine(path, "add");
151
delPath = Path.Combine(path, "del");
152
mutex = new Mutex(false, "mutex_" + Path.GetFileName(path));
155
internal void Begin()
158
if (!Directory.Exists(path))
160
Directory.CreateDirectory(path);
162
if (!Directory.Exists(addPath))
164
Directory.CreateDirectory(addPath);
166
if (!Directory.Exists(delPath))
168
Directory.CreateDirectory(delPath);
172
internal void BeginRead()
177
internal void EndRead()
179
mutex.ReleaseMutex();
182
internal void Commit()
188
Directory.Delete(path, true);
192
// Commit the deletes.
193
if (Directory.Exists(delPath))
195
Directory.Delete(delPath, true);
199
string [] files = Directory.GetFiles(addPath);
201
foreach (string f in files)
203
string filePath = Path.Combine(path, Path.GetFileName(f));
204
if (File.Exists(filePath))
206
File.Delete(filePath);
208
File.Move(f, filePath);
210
Directory.Delete(addPath, true);
216
mutex.ReleaseMutex();
219
internal void Abort()
223
deleteContainer = false;
225
Directory.Delete(addPath, true);
227
// Abort the Deletes.
228
string [] files = Directory.GetFiles(delPath);
230
foreach (string f in files)
232
string filePath = Path.Combine(path, Path.GetFileName(f));
233
if (File.Exists(filePath))
235
File.Delete(filePath);
237
File.Move(f, filePath);
239
Directory.Delete(delPath, true);
244
mutex.ReleaseMutex();
247
internal void addObject(string file, XmlElement xmlObject)
250
string filePath = Path.Combine(addPath, file);
251
XmlTextWriter xmlWriter = new XmlTextWriter(filePath, System.Text.Encoding.UTF8);
252
//xmlWriter.Formatting = Formatting.Indented;
253
xmlObject.WriteTo(xmlWriter);
257
internal void removeObject(string file)
259
string filePath = Path.Combine(path, file);
260
if (File.Exists(filePath))
262
File.Move(filePath, Path.Combine(delPath, file));
266
internal void DeleteContainer()
268
deleteContainer = true;
273
#region Private Methods.
274
internal static void DeleteStore(string path)
276
Directory.Delete(path, true);
277
Provider.Delete(Path.GetDirectoryName(path));
281
/// Method Used to create a store Object.
283
/// <param name="doc">XML document that describes the Objects</param>
284
/// <param name="trans">The transaction object for this create.</param>
285
private void CreateRecords(XmlDocument doc, Transaction trans)
287
XmlElement root = doc.DocumentElement;
288
XmlNodeList objectList = root.SelectNodes(XmlTags.ObjectTag);
290
// Build the path to the collection and make sure it exists.
291
if (!Directory.Exists(trans.path))
293
Directory.CreateDirectory(trans.path);
296
foreach (XmlElement recordEl in objectList)
298
// Get the Name, ID, and type.
299
string name = recordEl.GetAttribute(XmlTags.NameAttr);
300
string id = recordEl.GetAttribute(XmlTags.IdAttr);
301
string type = recordEl.GetAttribute(XmlTags.TypeAttr);
303
// Make sure this is a valid record.
304
if (name != null && id != null && type != null)
306
trans.addObject(id, recordEl);
312
/// Called to delete 1 or more Records.
314
/// <param name="doc">Xml string describing Records to delete.</param>
315
/// <param name="trans">The transaction object for this delete.</param>
316
private void DeleteRecords(XmlDocument doc, Transaction trans)
318
XmlElement root = doc.DocumentElement;
319
XmlNodeList objectList = root.SelectNodes(XmlTags.ObjectTag);
321
foreach (XmlElement recordEl in objectList)
324
string id = recordEl.GetAttribute(XmlTags.IdAttr);
325
trans.removeObject(id);
330
private void Dispose(bool inFinalize)
332
if (!AlreadyDisposed)
334
// Close all of the handles that have been opened.
339
//GC.SuppressFinalize(this);
341
AlreadyDisposed = true;
347
#region IProvider Methods
352
/// Called to Create a new Collection Store at the specified location.
354
public void CreateStore()
359
if (!Directory.Exists(DbPath))
361
Directory.CreateDirectory(DbPath);
363
conf.Version = version;
367
AlreadyDisposed = false;
372
/// Called to Delete the opened CollectionStore.
374
public void DeleteStore()
378
FsDb.DeleteDb = true;
384
/// Called to Open an existing Collection store at the specified location.
386
public void OpenStore()
391
if (!Directory.Exists(DbPath))
393
throw new OpenException(DbPath);
395
// Make sure the version is correct.
396
if (conf.Version != version)
398
throw new VersionException(DbPath, conf.Version, version);
402
AlreadyDisposed = false;
407
#region Container Calls.
410
/// Called to create a container to hold records. This call does not need to
411
/// be made. If a record is created and the container does not exist. it will be created.
413
/// <param name="name">The name of the container.</param>
414
public void CreateContainer(string name)
416
Transaction trans = new Transaction(DbPath, name);
422
/// Called to Delete a record container.
423
/// This call is deep (all records contained are deleted).
425
/// <param name="name">The name of the container.</param>
426
public void DeleteContainer(string name)
428
Transaction trans = new Transaction(DbPath, name);
430
trans.DeleteContainer();
436
#region Record Calls.
439
/// Used to Create, Modify or Delete records from the store.
441
/// <param name="container">The container to commit changes to.</param>
442
/// <param name="createDoc">The records to create or modify.</param>
443
/// <param name="deleteDoc">The records to delete.</param>
444
public void CommitRecords(string container, XmlDocument createDoc, XmlDocument deleteDoc)
446
Transaction trans = new Transaction(DbPath, container);
450
if (createDoc != null)
452
CreateRecords(createDoc, trans);
454
if (deleteDoc != null)
456
DeleteRecords(deleteDoc, trans);
469
/// Called to ge a Record. The record is returned as an XML string representation.
471
/// <param name="recordId">string that contains the ID of the Record to retrieve</param>
472
/// <param name="container">The conaiter to get the record from.</param>
473
/// <returns>XML string describing the Record</returns>
474
public XmlDocument GetRecord(string recordId, string container)
476
string recordPath = Path.Combine(DbPath, container);
477
recordPath = Path.Combine(recordPath, recordId);
478
XmlDocument doc = new XmlDocument();
479
XmlElement rootElement = doc.CreateElement(XmlTags.ObjectListTag);
480
doc.AppendChild(rootElement);
482
XmlTextReader xmlReader = new XmlTextReader(recordPath);
486
rootElement.InnerXml = xmlReader.ReadOuterXml();
502
/// Method used to search for Records using the specified query.
504
/// <param name="query">Query used for this search</param>
505
/// <returns></returns>
506
public IResultSet Search(Query query)
508
IResultSet resultSet = null;
509
string pattern = null;
510
bool valueCompare = false;
511
bool isAttribute = false;
512
string attribute = null;
516
switch (query.Property)
518
case BaseSchema.ObjectName:
519
attribute = XmlTags.NameAttr;
522
case BaseSchema.ObjectId:
523
attribute = XmlTags.IdAttr;
526
case BaseSchema.ObjectType:
527
attribute = XmlTags.TypeAttr;
532
switch (query.Operation)
535
case SearchOp.Not_Equal:
538
pattern = string.Format("<{0}[^>]*{1}=\"{2}\"[^>]*", XmlTags.ObjectTag, attribute, query.Value);
542
pattern = string.Format("<{0} name=\"{1}\" type=\"{2}\".*>{3}</{0}>", XmlTags.PropertyTag, query.Property, query.Type, query.Value);
545
case SearchOp.Begins:
548
pattern = string.Format("<{0}[^>]*{1}=\"{2}", XmlTags.ObjectTag, attribute, query.Value);
552
pattern = string.Format("<{0} name=\"{1}\" type=\"{2}\".*>{3}", XmlTags.PropertyTag, query.Property, query.Type, query.Value);
558
pattern = string.Format("<{0}[^>]*{1}=\"[^\"]*{2}\"", XmlTags.ObjectTag, attribute, query.Value);
562
pattern = string.Format("<{0} name=\"{1}\" type=\"{2}\".*>.*{3}</{0}>", XmlTags.PropertyTag, query.Property, query.Type, query.Value);
565
case SearchOp.Contains:
568
pattern = string.Format("<{0}[^>]*{1}=\"[^\"]*{2}", XmlTags.ObjectTag, attribute, query.Value);
572
pattern = string.Format("<{0} name=\"{1}\" type=\"{2}\".*>.*{3}.*</{0}>", XmlTags.PropertyTag, query.Property, query.Type, query.Value);
575
case SearchOp.Greater:
577
case SearchOp.Greater_Equal:
578
case SearchOp.Less_Equal:
579
pattern = string.Format("<{0} name=\"{1}\" type=\"{2}\"[^>]*>", XmlTags.PropertyTag, query.Property, query.Type);
582
case SearchOp.Exists:
585
pattern = string.Format("<{0}[^>]*{1}=\"[^>]*", XmlTags.ObjectTag, attribute, query.Value);
589
pattern = string.Format("<{0} name=\"{1}\" type=\"{2}\".*>", XmlTags.PropertyTag, query.Property, query.Type);
596
Regex expression = new Regex(pattern, RegexOptions.IgnoreCase);
600
if (query.CollectionId != null)
602
path = Path.Combine(DbPath, query.CollectionId);
603
files = Directory.GetFiles(path);
608
files = Directory.GetDirectories(path);
611
Queue resultQ = new Queue();
613
foreach(string sfile in files)
615
StreamReader sReader;
616
// If we have a collection ID then search in the collection only
617
// Else only search collections.
620
if (query.CollectionId != null)
622
sReader = File.OpenText(sfile);
626
sReader = File.OpenText(Path.Combine(sfile, Path.GetFileName(sfile)));
633
string xmlString = sReader.ReadToEnd();
635
Match match = expression.Match(xmlString);
636
if (match != Match.Empty)
642
while (match != Match.Empty)
644
int startIndex = match.Index + match.Length;
645
int length = xmlString.IndexOf('<', match.Index + match.Length) - startIndex;
646
string s1 = xmlString.Substring(startIndex, length);
651
case Syntax.DateTime:
652
case Syntax.TimeSpan:
658
ulong v1 = ulong.Parse(s1);
659
ulong v2 = ulong.Parse(query.Value);
660
diff = v1.CompareTo(v2);
668
long v1 = long.Parse(s1);
669
long v2 = long.Parse(query.Value);
670
diff = v1.CompareTo(v2);
675
Double v1 = Double.Parse(s1);
676
Double v2 = Double.Parse(query.Value);
677
diff = v1.CompareTo(v2);
681
throw new SearchException(query.ToString());
684
switch (query.Operation)
686
case SearchOp.Greater:
687
if (diff > 0) found = true;
690
if (diff < 0) found = true;
692
case SearchOp.Greater_Equal:
693
if (diff > 0 || diff == 0) found = true;
695
case SearchOp.Less_Equal:
696
if (diff < 0 || diff == 0) found = true;
702
string sObject = xmlString.Substring(0, xmlString.IndexOf('>')) + "/>";
703
resultQ.Enqueue(sObject);
708
match = match.NextMatch();
714
// Save this file as a match.
715
string sObject = xmlString.Substring(0, xmlString.IndexOf('>')) + "/>";
716
resultQ.Enqueue(sObject);
719
else if (query.Operation == SearchOp.Not_Equal)
721
// Save this as a miss.
722
string sObject = xmlString.Substring(0, xmlString.IndexOf('>')) + "/>";
723
resultQ.Enqueue(sObject);
726
resultSet = new FsResultSet(resultQ);
731
resultSet = new FsResultSet(new Queue());
743
/// Property to get the directory to where the store is rooted.
745
public Uri StoreDirectory
747
get {return new Uri(storePath);}
754
#region IDisposable Members
757
/// Method used to cleanup unmanaged resources.
759
public void Dispose()