Add copyright notices to all source files and put a license in LICENSE.
[versaplex.git] / versaplexd / vxdiskschema.cs
blob7c6eb5af3374787b9e0aa5eebae69f4daf2cf248
1 /*
2 * Versaplex:
3 * Copyright (C)2007-2008 Versabanq Innovations Inc. and contributors.
4 * See the included file named LICENSE for license information.
5 */
6 using System;
7 using System.Collections;
8 using System.Collections.Generic;
9 using System.IO;
10 using System.Text;
11 using System.Security.Cryptography;
12 using Wv;
13 using Wv.Extensions;
15 // An ISchemaBackend that uses a directory on disk as a backing store.
16 [WvMoniker]
17 internal class VxDiskSchema : ISchemaBackend
19 static WvLog log = new WvLog("VxDiskSchema", WvLog.L.Debug2);
21 private string exportdir;
23 public static void wvmoniker_register()
25 WvMoniker<ISchemaBackend>.register("dir",
26 (string m, object o) => new VxDiskSchema(m));
29 public VxDiskSchema(string _exportdir)
31 exportdir = _exportdir;
34 public void Dispose()
39 // The ISchemaBackend interface
42 // Export the current schema to the backing directory, in a format that can
43 // be read back later.
44 public VxSchemaErrors Put(VxSchema schema, VxSchemaChecksums sums,
45 VxPutOpts opts)
47 bool isbackup = (opts & VxPutOpts.IsBackup) != 0;
49 DirectoryInfo dir = new DirectoryInfo(exportdir);
50 dir.Create();
52 foreach (var p in schema)
54 if (!sums.ContainsKey(p.Key))
55 throw new ArgumentException("Missing checksum for " + p.Key);
57 VxSchemaElement elem = p.Value;
58 if (elem.text == null || elem.text == "")
59 DropSchema(new string[] {elem.key});
60 else
61 ExportToDisk(p.Value, sums[p.Key], isbackup);
64 // Writing schemas to disk doesn't give us any per-element errors.
65 return new VxSchemaErrors();
68 public VxSchema Get(IEnumerable<string> keys)
70 VxSchema fullschema = new VxSchema();
71 VxSchema schema = new VxSchema();
73 ReadExportedDir(fullschema, null);
75 if (keys == null)
76 return fullschema;
78 // This is a bit slow and stupid - we could just read only the
79 // required keys from disk. But the key-limiting is mainly for the
80 // much slower dbus and database backends, so it's probably not worth
81 // fixing.
82 foreach (string key in keys)
83 schema.Add(key, fullschema[key]);
85 if (schema.Count == 0)
86 schema = fullschema;
88 return schema;
91 public VxSchemaChecksums GetChecksums()
93 VxSchemaChecksums sums = new VxSchemaChecksums();
94 ReadExportedDir(null, sums);
95 return sums;
98 public VxSchemaErrors DropSchema(IEnumerable<string> keys)
100 VxSchemaErrors errs = new VxSchemaErrors();
102 foreach (string key in keys)
104 string fullpath = wv.PathCombine(exportdir, key);
105 log.print("Removing {0}\n", fullpath);
106 if (File.Exists(fullpath))
107 File.Delete(fullpath);
108 if (key.StartsWith("Index/"))
110 string type, name;
111 VxSchema.ParseKey(key, out type, out name);
112 if (type != "Index")
113 continue;
115 // If it was the last index for a table, remove the empty dir.
116 string[] split = wv.PathSplit(name);
117 if (split.Length > 0)
119 string table = split[0];
120 string tabpath = wv.PathCombine(exportdir, type, table);
121 // Directory.Delete won't delete non-empty dirs, but we
122 // still check both for safety and to write a sensible
123 // message.
124 if (Directory.GetFileSystemEntries(tabpath).Length == 0)
126 log.print("Removing empty directory {0}\n", tabpath);
127 Directory.Delete(tabpath);
133 return errs;
136 // Note: we ignore the "where" clause and just return everything.
137 public string GetSchemaData(string tablename, int seqnum, string where,
138 Dictionary<string,string> replaces, List<string> skipfields)
140 string datadir = Path.Combine(exportdir, "DATA");
141 string filename = wv.fmt("{0}-{1}.sql", seqnum, tablename);
142 string fullpath = Path.Combine(datadir, filename);
144 return File.ReadAllText(fullpath);
147 public void PutSchemaData(string tablename, string text, int seqnum)
149 string datadir = Path.Combine(exportdir, "DATA");
150 string filename = wv.fmt("{0}-{1}.sql", seqnum, tablename);
151 string fullpath = Path.Combine(datadir, filename);
153 Directory.CreateDirectory(datadir);
154 File.WriteAllBytes(fullpath, text.ToUTF8());
158 // Non-ISchemaBackend methods
161 // Retrieves both the schema and its checksums from exportdir, and puts
162 // them into the parameters.
163 private void ReadExportedDir(VxSchema schema, VxSchemaChecksums sums)
165 DirectoryInfo exportdirinfo = new DirectoryInfo(exportdir);
166 if (exportdirinfo.Exists)
168 // Read all files that match */* and Index/*/*.
169 foreach (DirectoryInfo dir1 in exportdirinfo.GetDirectories())
171 if (dir1.Name == "DATA")
172 continue;
174 string type = dir1.Name;
176 foreach (DirectoryInfo dir2 in dir1.GetDirectories())
178 if (dir2.Name == "DATA" || dir1.Name != "Index")
179 continue;
181 // This is the Index/*/* part
182 foreach (FileInfo file in dir2.GetFiles())
184 if (!IsFileNameUseful(file.Name))
185 continue;
187 string name = wv.PathCombine(dir2.Name, file.Name);
188 AddFromFile(file.FullName, type, name, schema, sums);
192 // This is the */* part
193 foreach (FileInfo file in dir1.GetFiles())
195 if (!IsFileNameUseful(file.Name))
196 continue;
198 AddFromFile(file.FullName, type, file.Name, schema, sums);
204 // Static methods
206 // We want to ignore hidden files, and backup files left by editors.
207 private static bool IsFileNameUseful(string filename)
209 return !filename.StartsWith(".") && !filename.EndsWith("~");
212 // Adds the contents of extradir to the provided schema and sums.
213 // Throws an ArgumentException if the directory contains an entry that
214 // already exists in schema or sums.
215 public static void AddFromDir(string extradir, VxSchema schema,
216 VxSchemaChecksums sums)
218 VxDiskSchema disk = new VxDiskSchema(extradir);
220 disk.ReadExportedDir(schema, sums);
223 // Reads a file from an on-disk exported schema, and sets the schema
224 // element parameter's text field, if the schema element isn't null.
225 // Returns a new VxSchemaChecksum object containing the checksum.
226 // Returns true if the file passes its MD5 validation.
227 // If it returns false, elem and sum may be set to null.
228 private static bool ReadSchemaFile(string filename, string type,
229 string name, out VxSchemaElement elem, out VxSchemaChecksum sum)
231 elem = null;
232 sum = null;
234 FileInfo fileinfo = new FileInfo(filename);
236 // Read the entire file into memory. C#'s file IO sucks.
237 byte[] bytes = File.ReadAllBytes(filename);
239 // Find the header line
240 int ii;
241 for (ii = 0; ii < bytes.Length; ii++)
242 if (bytes[ii] == '\n')
243 break;
245 if (ii == bytes.Length)
246 return false;
248 // Read the header line
249 Encoding utf8 = Encoding.UTF8;
250 string header = utf8.GetString(bytes, 0, ii).Replace("\r", "");
252 // Skip the newline
253 if (bytes[ii] == '\n')
254 ii++;
256 // Read the body
257 string body = utf8.GetString(bytes, ii, bytes.Length - ii);
258 elem = VxSchemaElement.create(type, name, body, false);
260 // Parse the header line
261 char[] space = {' '};
262 string[] headers = header.Split(space, 3);
263 if (headers.Length != 3)
264 return false;
266 string prefix = headers[0];
267 string header_md5 = headers[1];
268 string dbsum = headers[2];
270 if (prefix != "!!SCHEMAMATIC")
271 return false;
273 // Compute the hash of the rest of the file
274 byte[] md5 = MD5.Create().ComputeHash(bytes, ii,
275 (int)fileinfo.Length - ii);
276 string content_md5 = md5.ToHex().ToLower();
278 IEnumerable<ulong> sumlist;
280 // If the MD5 sums don't match, we want to make it obvious that the
281 // database and local file aren't in sync, so we don't load any actual
282 // checksums.
283 if (String.Compare(header_md5, content_md5, true) == 0)
285 string errctx = wv.fmt("Error while reading file {0}: ", filename);
286 sumlist = VxSchemaChecksum.ParseSumString(dbsum, errctx);
288 else
290 log.print(WvLog.L.Info, "Checksum mismatch for {0}\n", filename);
291 sumlist = new List<ulong>();
294 sum = new VxSchemaChecksum(elem.key, sumlist);
295 return true;
298 // Helper method to load a given on-disk element's schema and checksums
299 // into the container objects.
300 // Throws an ArgumentException if the schema or sums already contains the
301 // given key.
302 private static void AddFromFile(string path, string type, string name,
303 VxSchema schema, VxSchemaChecksums sums)
305 string key = wv.fmt("{0}/{1}", type, name);
307 // schema/sums.Add would throw an exception in this situation anyway,
308 // but it's nice to provide a more helpful error message.
309 if (schema != null && schema.ContainsKey(key))
310 throw new ArgumentException("Conflicting schema key: " + key);
311 if (sums != null && sums.ContainsKey(key))
312 throw new ArgumentException("Conflicting sums key: " + key);
314 VxSchemaChecksum sum;
315 VxSchemaElement elem;
316 ReadSchemaFile(path, type, name, out elem, out sum);
318 if (schema != null && elem != null)
319 schema.Add(key, elem);
320 if (sums != null && sum != null)
321 sums.Add(key, sum);
324 private void ExportToDisk(VxSchemaElement elem, VxSchemaChecksum sum,
325 bool isbackup)
327 // Make some kind of attempt to run on Windows.
328 string filename = wv.PathJoin(exportdir, elem.type, elem.name);
330 // Make directories
331 Directory.CreateDirectory(Path.GetDirectoryName(filename));
333 string suffix = "";
334 if (isbackup)
336 int i = 1;
337 while(File.Exists(filename + "-" + i))
338 i++;
339 suffix = "-" + i;
342 filename += suffix;
344 log.print("Writing {0}\n", filename);
345 File.WriteAllBytes(filename, elem.ToStringWithHeader(sum).ToUTF8());