Transport no longer has a Stream member.
[versaplex.git] / versaplexd / vxdiskschema.cs
blob5c329ef3e4248ed0cc81cf93c781ab9fec97368c
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;
30 // The ISchemaBackend interface
33 // Export the current schema to the backing directory, in a format that can
34 // be read back later.
35 public VxSchemaErrors Put(VxSchema schema, VxSchemaChecksums sums,
36 VxPutOpts opts)
38 bool isbackup = (opts & VxPutOpts.IsBackup) != 0;
40 DirectoryInfo dir = new DirectoryInfo(exportdir);
41 dir.Create();
43 foreach (var p in schema)
45 if (!sums.ContainsKey(p.Key))
46 throw new ArgumentException("Missing checksum for " + p.Key);
48 VxSchemaElement elem = p.Value;
49 if (elem.text == null || elem.text == "")
50 DropSchema(new string[] {elem.key});
51 else
52 ExportToDisk(p.Value, sums[p.Key], isbackup);
55 // Writing schemas to disk doesn't give us any per-element errors.
56 return new VxSchemaErrors();
59 public VxSchema Get(IEnumerable<string> keys)
61 VxSchema fullschema = new VxSchema();
62 VxSchema schema = new VxSchema();
64 ReadExportedDir(fullschema, null);
66 if (keys == null)
67 return fullschema;
69 // This is a bit slow and stupid - we could just read only the
70 // required keys from disk. But the key-limiting is mainly for the
71 // much slower dbus and database backends, so it's probably not worth
72 // fixing.
73 foreach (string key in keys)
74 schema.Add(key, fullschema[key]);
76 if (schema.Count == 0)
77 schema = fullschema;
79 return schema;
82 public VxSchemaChecksums GetChecksums()
84 VxSchemaChecksums sums = new VxSchemaChecksums();
85 ReadExportedDir(null, sums);
86 return sums;
89 public VxSchemaErrors DropSchema(IEnumerable<string> keys)
91 VxSchemaErrors errs = new VxSchemaErrors();
93 foreach (string key in keys)
95 string fullpath = wv.PathCombine(exportdir, key);
96 log.print("Removing {0}\n", fullpath);
97 if (File.Exists(fullpath))
98 File.Delete(fullpath);
99 if (key.StartsWith("Index"))
101 string type, name;
102 VxSchema.ParseKey(key, out type, out name);
103 if (type != "Index")
104 continue;
106 // If it was the last index for a table, remove the empty dir.
107 string[] split = name.Split('/');
108 if (split.Length > 0)
110 string table = split[0];
111 string tabpath = wv.PathCombine(exportdir, type, table);
112 // Directory.Delete won't delete non-empty dirs, but we
113 // still check both for safety and to write a sensible
114 // message.
115 if (Directory.GetFileSystemEntries(tabpath).Length == 0)
117 log.print("Removing empty directory {0}\n", tabpath);
118 Directory.Delete(tabpath);
124 return errs;
127 // Note: we ignore the "where" clause and just return everything.
128 public string GetSchemaData(string tablename, int seqnum, string where)
130 string datadir = Path.Combine(exportdir, "DATA");
131 string filename = wv.fmt("{0}-{1}.sql", seqnum, tablename);
132 string fullpath = Path.Combine(datadir, filename);
134 return File.ReadAllText(fullpath);
137 public void PutSchemaData(string tablename, string text, int seqnum)
139 string datadir = Path.Combine(exportdir, "DATA");
140 string filename = wv.fmt("{0}-{1}.sql", seqnum, tablename);
141 string fullpath = Path.Combine(datadir, filename);
143 Directory.CreateDirectory(datadir);
144 File.WriteAllBytes(fullpath, text.ToUTF8());
148 // Non-ISchemaBackend methods
151 // Retrieves both the schema and its checksums from exportdir, and puts
152 // them into the parameters.
153 private void ReadExportedDir(VxSchema schema, VxSchemaChecksums sums)
155 DirectoryInfo exportdirinfo = new DirectoryInfo(exportdir);
156 if (exportdirinfo.Exists)
158 // Read all files that match */* and Index/*/*.
159 foreach (DirectoryInfo dir1 in exportdirinfo.GetDirectories())
161 if (dir1.Name == "DATA")
162 continue;
164 string type = dir1.Name;
166 foreach (DirectoryInfo dir2 in dir1.GetDirectories())
168 if (dir2.Name == "DATA" || dir1.Name != "Index")
169 continue;
171 // This is the Index/*/* part
172 foreach (FileInfo file in dir2.GetFiles())
174 if (!IsFileNameUseful(file.Name))
175 continue;
177 string name = wv.PathCombine(dir2.Name, file.Name);
178 AddFromFile(file.FullName, type, name, schema, sums);
182 // This is the */* part
183 foreach (FileInfo file in dir1.GetFiles())
185 if (!IsFileNameUseful(file.Name))
186 continue;
188 AddFromFile(file.FullName, type, file.Name, schema, sums);
194 // Static methods
196 // We want to ignore hidden files, and backup files left by editors.
197 private static bool IsFileNameUseful(string filename)
199 return !filename.StartsWith(".") && !filename.EndsWith("~");
202 // Adds the contents of extradir to the provided schema and sums.
203 // Throws an ArgumentException if the directory contains an entry that
204 // already exists in schema or sums.
205 public static void AddFromDir(string extradir, VxSchema schema,
206 VxSchemaChecksums sums)
208 VxDiskSchema disk = new VxDiskSchema(extradir);
210 disk.ReadExportedDir(schema, sums);
213 // Reads a file from an on-disk exported schema, and sets the schema
214 // element parameter's text field, if the schema element isn't null.
215 // Returns a new VxSchemaChecksum object containing the checksum.
216 // Returns true if the file passes its MD5 validation.
217 // If it returns false, elem and sum may be set to null.
218 private static bool ReadSchemaFile(string filename, string type,
219 string name, out VxSchemaElement elem, out VxSchemaChecksum sum)
221 elem = null;
222 sum = null;
224 FileInfo fileinfo = new FileInfo(filename);
226 // Read the entire file into memory. C#'s file IO sucks.
227 byte[] bytes = File.ReadAllBytes(filename);
229 // Find the header line
230 int ii;
231 for (ii = 0; ii < bytes.Length; ii++)
232 if (bytes[ii] == '\n')
233 break;
235 if (ii == bytes.Length)
236 return false;
238 // Read the header line
239 Encoding utf8 = Encoding.UTF8;
240 string header = utf8.GetString(bytes, 0, ii).Replace("\r", "");
242 // Skip the newline
243 if (bytes[ii] == '\n')
244 ii++;
246 // Read the body
247 string body = utf8.GetString(bytes, ii, bytes.Length - ii);
248 elem = new VxSchemaElement(type, name, body, false);
250 // Parse the header line
251 char[] space = {' '};
252 string[] headers = header.Split(space, 3);
253 if (headers.Length != 3)
254 return false;
256 string prefix = headers[0];
257 string header_md5 = headers[1];
258 string dbsum = headers[2];
260 if (prefix != "!!SCHEMAMATIC")
261 return false;
263 // Compute the hash of the rest of the file
264 byte[] md5 = MD5.Create().ComputeHash(bytes, ii,
265 (int)fileinfo.Length - ii);
266 string content_md5 = md5.ToHex().ToLower();
268 IEnumerable<ulong> sumlist;
270 // If the MD5 sums don't match, we want to make it obvious that the
271 // database and local file aren't in sync, so we don't load any actual
272 // checksums.
273 if (String.Compare(header_md5, content_md5, true) == 0)
275 string errctx = wv.fmt("Error while reading file {0}: ", filename);
276 sumlist = VxSchemaChecksum.ParseSumString(dbsum, errctx);
278 else
280 log.print(WvLog.L.Info, "Checksum mismatch for {0}\n", filename);
281 sumlist = new List<ulong>();
284 sum = new VxSchemaChecksum(elem.key, sumlist);
285 return true;
288 // Helper method to load a given on-disk element's schema and checksums
289 // into the container objects.
290 // Throws an ArgumentException if the schema or sums already contains the
291 // given key.
292 private static void AddFromFile(string path, string type, string name,
293 VxSchema schema, VxSchemaChecksums sums)
295 string key = wv.PathCombine(type, name);
297 // schema/sums.Add would throw an exception in this situation anyway,
298 // but it's nice to provide a more helpful error message.
299 if (schema != null && schema.ContainsKey(key))
300 throw new ArgumentException("Conflicting schema key: " + key);
301 if (sums != null && sums.ContainsKey(key))
302 throw new ArgumentException("Conflicting sums key: " + key);
304 VxSchemaChecksum sum;
305 VxSchemaElement elem;
306 ReadSchemaFile(path, type, name, out elem, out sum);
308 if (schema != null && elem != null)
309 schema.Add(key, elem);
310 if (sums != null && sum != null)
311 sums.Add(key, sum);
314 private void ExportToDisk(VxSchemaElement elem, VxSchemaChecksum sum,
315 bool isbackup)
317 // Make some kind of attempt to run on Windows.
318 string filename = (exportdir + "/" + elem.key).Replace(
319 '/', Path.DirectorySeparatorChar);
321 // Make directories
322 Directory.CreateDirectory(Path.GetDirectoryName(filename));
324 string suffix = "";
325 if (isbackup)
327 int i = 1;
328 while(File.Exists(filename + "-" + i))
329 i++;
330 suffix = "-" + i;
333 filename += suffix;
335 log.print("Writing {0}\n", filename);
336 File.WriteAllBytes(filename, elem.ToStringWithHeader(sum).ToUTF8());