Fix schemamatic unit tests to work with eduardo's GetSchemaData() changes.
[versaplex.git] / versaplexd / vxdiskschema.cs
blobeb9cabe4e3865fdae5b12f7c66ae6fccd1398324
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.IO;
5 using System.Text;
6 using System.Security.Cryptography;
7 using Wv;
8 using Wv.Extensions;
10 // An ISchemaBackend that uses a directory on disk as a backing store.
11 [WvMoniker]
12 internal class VxDiskSchema : ISchemaBackend
14 static WvLog log = new WvLog("VxDiskSchema", WvLog.L.Debug2);
16 private string exportdir;
18 public static void wvmoniker_register()
20 WvMoniker<ISchemaBackend>.register("dir",
21 (string m, object o) => new VxDiskSchema(m));
24 public VxDiskSchema(string _exportdir)
26 exportdir = _exportdir;
29 public void Dispose()
34 // The ISchemaBackend interface
37 // Export the current schema to the backing directory, in a format that can
38 // be read back later.
39 public VxSchemaErrors Put(VxSchema schema, VxSchemaChecksums sums,
40 VxPutOpts opts)
42 bool isbackup = (opts & VxPutOpts.IsBackup) != 0;
44 DirectoryInfo dir = new DirectoryInfo(exportdir);
45 dir.Create();
47 foreach (var p in schema)
49 if (!sums.ContainsKey(p.Key))
50 throw new ArgumentException("Missing checksum for " + p.Key);
52 VxSchemaElement elem = p.Value;
53 if (elem.text == null || elem.text == "")
54 DropSchema(new string[] {elem.key});
55 else
56 ExportToDisk(p.Value, sums[p.Key], isbackup);
59 // Writing schemas to disk doesn't give us any per-element errors.
60 return new VxSchemaErrors();
63 public VxSchema Get(IEnumerable<string> keys)
65 VxSchema fullschema = new VxSchema();
66 VxSchema schema = new VxSchema();
68 ReadExportedDir(fullschema, null);
70 if (keys == null)
71 return fullschema;
73 // This is a bit slow and stupid - we could just read only the
74 // required keys from disk. But the key-limiting is mainly for the
75 // much slower dbus and database backends, so it's probably not worth
76 // fixing.
77 foreach (string key in keys)
78 schema.Add(key, fullschema[key]);
80 if (schema.Count == 0)
81 schema = fullschema;
83 return schema;
86 public VxSchemaChecksums GetChecksums()
88 VxSchemaChecksums sums = new VxSchemaChecksums();
89 ReadExportedDir(null, sums);
90 return sums;
93 public VxSchemaErrors DropSchema(IEnumerable<string> keys)
95 VxSchemaErrors errs = new VxSchemaErrors();
97 foreach (string key in keys)
99 string fullpath = wv.PathCombine(exportdir, key);
100 log.print("Removing {0}\n", fullpath);
101 if (File.Exists(fullpath))
102 File.Delete(fullpath);
103 if (key.StartsWith("Index/"))
105 string type, name;
106 VxSchema.ParseKey(key, out type, out name);
107 if (type != "Index")
108 continue;
110 // If it was the last index for a table, remove the empty dir.
111 string[] split = wv.PathSplit(name);
112 if (split.Length > 0)
114 string table = split[0];
115 string tabpath = wv.PathCombine(exportdir, type, table);
116 // Directory.Delete won't delete non-empty dirs, but we
117 // still check both for safety and to write a sensible
118 // message.
119 if (Directory.GetFileSystemEntries(tabpath).Length == 0)
121 log.print("Removing empty directory {0}\n", tabpath);
122 Directory.Delete(tabpath);
128 return errs;
131 // Note: we ignore the "where" clause and just return everything.
132 public string GetSchemaData(string tablename, int seqnum, string where,
133 Dictionary<string,string> replaces, List<string> skipfields)
135 string datadir = Path.Combine(exportdir, "DATA");
136 string filename = wv.fmt("{0}-{1}.sql", seqnum, tablename);
137 string fullpath = Path.Combine(datadir, filename);
139 return File.ReadAllText(fullpath);
142 public void PutSchemaData(string tablename, string text, int seqnum)
144 string datadir = Path.Combine(exportdir, "DATA");
145 string filename = wv.fmt("{0}-{1}.sql", seqnum, tablename);
146 string fullpath = Path.Combine(datadir, filename);
148 Directory.CreateDirectory(datadir);
149 File.WriteAllBytes(fullpath, text.ToUTF8());
153 // Non-ISchemaBackend methods
156 // Retrieves both the schema and its checksums from exportdir, and puts
157 // them into the parameters.
158 private void ReadExportedDir(VxSchema schema, VxSchemaChecksums sums)
160 DirectoryInfo exportdirinfo = new DirectoryInfo(exportdir);
161 if (exportdirinfo.Exists)
163 // Read all files that match */* and Index/*/*.
164 foreach (DirectoryInfo dir1 in exportdirinfo.GetDirectories())
166 if (dir1.Name == "DATA")
167 continue;
169 string type = dir1.Name;
171 foreach (DirectoryInfo dir2 in dir1.GetDirectories())
173 if (dir2.Name == "DATA" || dir1.Name != "Index")
174 continue;
176 // This is the Index/*/* part
177 foreach (FileInfo file in dir2.GetFiles())
179 if (!IsFileNameUseful(file.Name))
180 continue;
182 string name = wv.PathCombine(dir2.Name, file.Name);
183 AddFromFile(file.FullName, type, name, schema, sums);
187 // This is the */* part
188 foreach (FileInfo file in dir1.GetFiles())
190 if (!IsFileNameUseful(file.Name))
191 continue;
193 AddFromFile(file.FullName, type, file.Name, schema, sums);
199 // Static methods
201 // We want to ignore hidden files, and backup files left by editors.
202 private static bool IsFileNameUseful(string filename)
204 return !filename.StartsWith(".") && !filename.EndsWith("~");
207 // Adds the contents of extradir to the provided schema and sums.
208 // Throws an ArgumentException if the directory contains an entry that
209 // already exists in schema or sums.
210 public static void AddFromDir(string extradir, VxSchema schema,
211 VxSchemaChecksums sums)
213 VxDiskSchema disk = new VxDiskSchema(extradir);
215 disk.ReadExportedDir(schema, sums);
218 // Reads a file from an on-disk exported schema, and sets the schema
219 // element parameter's text field, if the schema element isn't null.
220 // Returns a new VxSchemaChecksum object containing the checksum.
221 // Returns true if the file passes its MD5 validation.
222 // If it returns false, elem and sum may be set to null.
223 private static bool ReadSchemaFile(string filename, string type,
224 string name, out VxSchemaElement elem, out VxSchemaChecksum sum)
226 elem = null;
227 sum = null;
229 FileInfo fileinfo = new FileInfo(filename);
231 // Read the entire file into memory. C#'s file IO sucks.
232 byte[] bytes = File.ReadAllBytes(filename);
234 // Find the header line
235 int ii;
236 for (ii = 0; ii < bytes.Length; ii++)
237 if (bytes[ii] == '\n')
238 break;
240 if (ii == bytes.Length)
241 return false;
243 // Read the header line
244 Encoding utf8 = Encoding.UTF8;
245 string header = utf8.GetString(bytes, 0, ii).Replace("\r", "");
247 // Skip the newline
248 if (bytes[ii] == '\n')
249 ii++;
251 // Read the body
252 string body = utf8.GetString(bytes, ii, bytes.Length - ii);
253 elem = VxSchemaElement.create(type, name, body, false);
255 // Parse the header line
256 char[] space = {' '};
257 string[] headers = header.Split(space, 3);
258 if (headers.Length != 3)
259 return false;
261 string prefix = headers[0];
262 string header_md5 = headers[1];
263 string dbsum = headers[2];
265 if (prefix != "!!SCHEMAMATIC")
266 return false;
268 // Compute the hash of the rest of the file
269 byte[] md5 = MD5.Create().ComputeHash(bytes, ii,
270 (int)fileinfo.Length - ii);
271 string content_md5 = md5.ToHex().ToLower();
273 IEnumerable<ulong> sumlist;
275 // If the MD5 sums don't match, we want to make it obvious that the
276 // database and local file aren't in sync, so we don't load any actual
277 // checksums.
278 if (String.Compare(header_md5, content_md5, true) == 0)
280 string errctx = wv.fmt("Error while reading file {0}: ", filename);
281 sumlist = VxSchemaChecksum.ParseSumString(dbsum, errctx);
283 else
285 log.print(WvLog.L.Info, "Checksum mismatch for {0}\n", filename);
286 sumlist = new List<ulong>();
289 sum = new VxSchemaChecksum(elem.key, sumlist);
290 return true;
293 // Helper method to load a given on-disk element's schema and checksums
294 // into the container objects.
295 // Throws an ArgumentException if the schema or sums already contains the
296 // given key.
297 private static void AddFromFile(string path, string type, string name,
298 VxSchema schema, VxSchemaChecksums sums)
300 string key = wv.fmt("{0}/{1}", type, name);
302 // schema/sums.Add would throw an exception in this situation anyway,
303 // but it's nice to provide a more helpful error message.
304 if (schema != null && schema.ContainsKey(key))
305 throw new ArgumentException("Conflicting schema key: " + key);
306 if (sums != null && sums.ContainsKey(key))
307 throw new ArgumentException("Conflicting sums key: " + key);
309 VxSchemaChecksum sum;
310 VxSchemaElement elem;
311 ReadSchemaFile(path, type, name, out elem, out sum);
313 if (schema != null && elem != null)
314 schema.Add(key, elem);
315 if (sums != null && sum != null)
316 sums.Add(key, sum);
319 private void ExportToDisk(VxSchemaElement elem, VxSchemaChecksum sum,
320 bool isbackup)
322 // Make some kind of attempt to run on Windows.
323 string filename = wv.PathJoin(exportdir, elem.type, elem.name);
325 // Make directories
326 Directory.CreateDirectory(Path.GetDirectoryName(filename));
328 string suffix = "";
329 if (isbackup)
331 int i = 1;
332 while(File.Exists(filename + "-" + i))
333 i++;
334 suffix = "-" + i;
337 filename += suffix;
339 log.print("Writing {0}\n", filename);
340 File.WriteAllBytes(filename, elem.ToStringWithHeader(sum).ToUTF8());