2 using System
.Collections
;
3 using System
.Collections
.Generic
;
6 using System
.Security
.Cryptography
;
10 // An ISchemaBackend that uses a directory on disk as a backing store.
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
,
38 bool isbackup
= (opts
& VxPutOpts
.IsBackup
) != 0;
40 DirectoryInfo dir
= new DirectoryInfo(exportdir
);
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}
);
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);
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
73 foreach (string key
in keys
)
74 schema
.Add(key
, fullschema
[key
]);
76 if (schema
.Count
== 0)
82 public VxSchemaChecksums
GetChecksums()
84 VxSchemaChecksums sums
= new VxSchemaChecksums();
85 ReadExportedDir(null, 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"))
102 VxSchema
.ParseKey(key
, out type
, out name
);
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
115 if (Directory
.GetFileSystemEntries(tabpath
).Length
== 0)
117 log
.print("Removing empty directory {0}\n", tabpath
);
118 Directory
.Delete(tabpath
);
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")
164 string type
= dir1
.Name
;
166 foreach (DirectoryInfo dir2
in dir1
.GetDirectories())
168 if (dir2
.Name
== "DATA" || dir1
.Name
!= "Index")
171 // This is the Index/*/* part
172 foreach (FileInfo file
in dir2
.GetFiles())
174 if (!IsFileNameUseful(file
.Name
))
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
))
188 AddFromFile(file
.FullName
, type
, file
.Name
, schema
, sums
);
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
)
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
231 for (ii
= 0; ii
< bytes
.Length
; ii
++)
232 if (bytes
[ii
] == '\n')
235 if (ii
== bytes
.Length
)
238 // Read the header line
239 Encoding utf8
= Encoding
.UTF8
;
240 string header
= utf8
.GetString(bytes
, 0, ii
).Replace("\r", "");
243 if (bytes
[ii
] == '\n')
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)
256 string prefix
= headers
[0];
257 string header_md5
= headers
[1];
258 string dbsum
= headers
[2];
260 if (prefix
!= "!!SCHEMAMATIC")
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
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
);
280 log
.print(WvLog
.L
.Info
, "Checksum mismatch for {0}\n", filename
);
281 sumlist
= new List
<ulong>();
284 sum
= new VxSchemaChecksum(elem
.key
, sumlist
);
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
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)
314 private void ExportToDisk(VxSchemaElement elem
, VxSchemaChecksum sum
,
317 // Make some kind of attempt to run on Windows.
318 string filename
= (exportdir
+ "/" + elem
.key
).Replace(
319 '/', Path
.DirectorySeparatorChar
);
322 Directory
.CreateDirectory(Path
.GetDirectoryName(filename
));
328 while(File
.Exists(filename
+ "-" + i
))
335 log
.print("Writing {0}\n", filename
);
336 File
.WriteAllBytes(filename
, elem
.ToStringWithHeader(sum
).ToUTF8());