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
;
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
,
42 bool isbackup
= (opts
& VxPutOpts
.IsBackup
) != 0;
44 DirectoryInfo dir
= new DirectoryInfo(exportdir
);
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}
);
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);
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
77 foreach (string key
in keys
)
78 schema
.Add(key
, fullschema
[key
]);
80 if (schema
.Count
== 0)
86 public VxSchemaChecksums
GetChecksums()
88 VxSchemaChecksums sums
= new VxSchemaChecksums();
89 ReadExportedDir(null, 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/"))
106 VxSchema
.ParseKey(key
, out type
, out name
);
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
119 if (Directory
.GetFileSystemEntries(tabpath
).Length
== 0)
121 log
.print("Removing empty directory {0}\n", tabpath
);
122 Directory
.Delete(tabpath
);
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")
169 string type
= dir1
.Name
;
171 foreach (DirectoryInfo dir2
in dir1
.GetDirectories())
173 if (dir2
.Name
== "DATA" || dir1
.Name
!= "Index")
176 // This is the Index/*/* part
177 foreach (FileInfo file
in dir2
.GetFiles())
179 if (!IsFileNameUseful(file
.Name
))
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
))
193 AddFromFile(file
.FullName
, type
, file
.Name
, schema
, sums
);
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
)
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
236 for (ii
= 0; ii
< bytes
.Length
; ii
++)
237 if (bytes
[ii
] == '\n')
240 if (ii
== bytes
.Length
)
243 // Read the header line
244 Encoding utf8
= Encoding
.UTF8
;
245 string header
= utf8
.GetString(bytes
, 0, ii
).Replace("\r", "");
248 if (bytes
[ii
] == '\n')
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)
261 string prefix
= headers
[0];
262 string header_md5
= headers
[1];
263 string dbsum
= headers
[2];
265 if (prefix
!= "!!SCHEMAMATIC")
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
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
);
285 log
.print(WvLog
.L
.Info
, "Checksum mismatch for {0}\n", filename
);
286 sumlist
= new List
<ulong>();
289 sum
= new VxSchemaChecksum(elem
.key
, sumlist
);
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
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)
319 private void ExportToDisk(VxSchemaElement elem
, VxSchemaChecksum sum
,
322 // Make some kind of attempt to run on Windows.
323 string filename
= wv
.PathJoin(exportdir
, elem
.type
, elem
.name
);
326 Directory
.CreateDirectory(Path
.GetDirectoryName(filename
));
332 while(File
.Exists(filename
+ "-" + i
))
339 log
.print("Writing {0}\n", filename
);
340 File
.WriteAllBytes(filename
, elem
.ToStringWithHeader(sum
).ToUTF8());