3 * Copyright (C)2007-2008 Versabanq Innovations Inc. and contributors.
4 * See the included file named LICENSE for license information.
7 using System
.Collections
;
8 using System
.Collections
.Generic
;
11 using System
.Security
.Cryptography
;
15 // An ISchemaBackend that uses a directory on disk as a backing store.
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
;
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
,
47 bool isbackup
= (opts
& VxPutOpts
.IsBackup
) != 0;
49 DirectoryInfo dir
= new DirectoryInfo(exportdir
);
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}
);
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);
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
82 foreach (string key
in keys
)
83 schema
.Add(key
, fullschema
[key
]);
85 if (schema
.Count
== 0)
91 public VxSchemaChecksums
GetChecksums()
93 VxSchemaChecksums sums
= new VxSchemaChecksums();
94 ReadExportedDir(null, 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/"))
111 VxSchema
.ParseKey(key
, out type
, out name
);
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
124 if (Directory
.GetFileSystemEntries(tabpath
).Length
== 0)
126 log
.print("Removing empty directory {0}\n", tabpath
);
127 Directory
.Delete(tabpath
);
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")
174 string type
= dir1
.Name
;
176 foreach (DirectoryInfo dir2
in dir1
.GetDirectories())
178 if (dir2
.Name
== "DATA" || dir1
.Name
!= "Index")
181 // This is the Index/*/* part
182 foreach (FileInfo file
in dir2
.GetFiles())
184 if (!IsFileNameUseful(file
.Name
))
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
))
198 AddFromFile(file
.FullName
, type
, file
.Name
, schema
, sums
);
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
)
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
241 for (ii
= 0; ii
< bytes
.Length
; ii
++)
242 if (bytes
[ii
] == '\n')
245 if (ii
== bytes
.Length
)
248 // Read the header line
249 Encoding utf8
= Encoding
.UTF8
;
250 string header
= utf8
.GetString(bytes
, 0, ii
).Replace("\r", "");
253 if (bytes
[ii
] == '\n')
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)
266 string prefix
= headers
[0];
267 string header_md5
= headers
[1];
268 string dbsum
= headers
[2];
270 if (prefix
!= "!!SCHEMAMATIC")
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
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
);
290 log
.print(WvLog
.L
.Info
, "Checksum mismatch for {0}\n", filename
);
291 sumlist
= new List
<ulong>();
294 sum
= new VxSchemaChecksum(elem
.key
, sumlist
);
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
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)
324 private void ExportToDisk(VxSchemaElement elem
, VxSchemaChecksum sum
,
327 // Make some kind of attempt to run on Windows.
328 string filename
= wv
.PathJoin(exportdir
, elem
.type
, elem
.name
);
331 Directory
.CreateDirectory(Path
.GetDirectoryName(filename
));
337 while(File
.Exists(filename
+ "-" + i
))
344 log
.print("Writing {0}\n", filename
);
345 File
.WriteAllBytes(filename
, elem
.ToStringWithHeader(sum
).ToUTF8());