1 module unde.command_line.db;
2 
3 import berkeleydb.all;
4 import std.bitmanip;
5 import std..string;
6 import std.stdio;
7 
8 import unde.global_state;
9 
10 struct command_key
11 {
12     string cwd;
13     ulong id;
14 }
15 
16 struct command_data
17 {
18     string command;
19     ulong start;
20     ulong end;
21     int status;
22 }
23 
24 string
25 get_key_for_command(command_key k)
26 {
27     string key_string;
28     key_string ~= nativeToBigEndian(cast(ushort)k.cwd.length);
29     key_string ~= k.cwd;
30     key_string ~= nativeToBigEndian(k.id);
31     return key_string;
32 }
33 
34 void
35 parse_key_for_command(in string key_string, out command_key k)
36 {
37     ubyte[k.id.sizeof] id = (cast(ubyte[])key_string)[$-k.id.sizeof..$]; 
38     k.id = bigEndianToNative!ulong(id);
39     k.cwd = key_string[ushort.sizeof..$-k.id.sizeof];
40     ubyte[ushort.sizeof] cwd_len_bytes = (cast(ubyte[])key_string)[0..ushort.sizeof]; 
41     ushort cwd_len = bigEndianToNative!ushort(cwd_len_bytes);
42     assert(k.cwd.length == cwd_len);
43 }
44 
45 unittest
46 {
47     command_key cmd_key = command_key("Some Text", 0xABCDEF0123456789);
48     string key_string = get_key_for_command(cmd_key);
49     command_key cmd_key2;
50     parse_key_for_command(key_string, cmd_key2);
51     assert(cmd_key == cmd_key2, format("%s=%s, %X=%X", 
52                 cmd_key.cwd, cmd_key2.cwd, cmd_key.id, cmd_key2.id));
53 }
54 
55 string
56 get_data_for_command(command_data d)
57 {
58     string data_string;
59     data_string = d.command;
60     data_string ~= (cast(char*)&d.start)[0..d.start.sizeof];
61     data_string ~= (cast(char*)&d.end)[0..d.end.sizeof];
62     data_string ~= (cast(char*)&d.status)[0..d.status.sizeof];
63     return data_string;
64 }
65 
66 void
67 parse_data_for_command(string data_string, out command_data d)
68 {
69     d.status = *cast(int*)data_string[$-d.status.sizeof..$];
70     data_string = data_string[0..$-d.status.sizeof];
71     d.end = *cast(ulong*)data_string[$-d.end.sizeof..$];
72     data_string = data_string[0..$-d.end.sizeof];
73     d.start = *cast(ulong*)data_string[$-d.start.sizeof..$];
74     d.command = data_string[0..$-d.start.sizeof];
75 }
76 
77 unittest
78 {
79     command_data cmd_data = command_data("Some Command", 0xABCDEF0123456789,
80             0xFED347891ABCF356, 0xAF102545);
81     string data_string = get_data_for_command(cmd_data);
82     command_data cmd_data2;
83     parse_data_for_command(data_string, cmd_data2);
84     assert(cmd_data == cmd_data2);
85 }
86 
87 struct command_out_key
88 {
89     string cwd;
90     ulong cmd_id;
91     ulong out_id;
92 }
93 
94 enum OutPipe
95 {
96     STDOUT,
97     STDERR
98 }
99 
100 enum CommandsOutVersion
101 {
102     Simple,
103     Screen
104 }
105 
106 struct command_out_data
107 {
108     CommandsOutVersion vers;
109     ulong time;
110     OutPipe pipe;
111     size_t pos;
112     union
113     {
114         struct
115         {
116             int len;
117             ushort[] attrs;
118             string output;
119         };
120         struct
121         {
122             int cols;
123             int rows;
124             dchar[] screen;
125             ushort[] scr_attrs;
126         }
127     }
128 
129     this(ulong time, OutPipe pipe, size_t pos, string output)
130     {
131         this.time = time;
132         this.pipe = pipe;
133         this.pos = pos;
134         this.output = output;
135     }
136     this(ulong time, OutPipe pipe, size_t pos, string output, ushort[] attrs)
137     {
138         this.time = time;
139         this.pipe = pipe;
140         this.pos = pos;
141         this.output = output;
142         this.len = cast(int)attrs.length;
143         this.attrs = attrs;
144     }
145     this(ulong time, OutPipe pipe, size_t pos, int cols, int rows, dchar[] screen, ushort[] scr_attrs)
146     {
147         this.vers = CommandsOutVersion.Screen;
148         this.time = time;
149         this.pipe = pipe;
150         this.pos = pos;
151         this.cols = cols;
152         this.rows = rows;
153         this.screen = screen;
154         this.scr_attrs = scr_attrs;
155     }
156 }
157 
158 string
159 get_key_for_command_out(command_out_key k)
160 {
161     string key_string;
162     key_string = k.cwd;
163     key_string ~= nativeToBigEndian(k.cmd_id);
164     key_string ~= nativeToBigEndian(k.out_id);
165     return key_string;
166 }
167 
168 void
169 parse_key_for_command_out(string key_string, out command_out_key k)
170 {
171     ubyte[k.out_id.sizeof] out_id = (cast(ubyte[])key_string)[$-k.out_id.sizeof..$]; 
172     k.out_id = bigEndianToNative!ulong(out_id);
173     key_string = key_string[0..$-k.out_id.sizeof];
174     ubyte[k.cmd_id.sizeof] cmd_id = (cast(ubyte[])key_string)[$-k.cmd_id.sizeof..$]; 
175     k.cmd_id = bigEndianToNative!ulong(cmd_id);
176     k.cwd = key_string[0..$-k.cmd_id.sizeof];
177 }
178 
179 unittest
180 {
181     command_out_key cmd_key = command_out_key("Some Text", 0xABCDEF0123456789, 0xFED347891ABCF356);
182     string key_string = get_key_for_command_out(cmd_key);
183     command_out_key cmd_key2;
184     parse_key_for_command_out(key_string, cmd_key2);
185     assert(cmd_key == cmd_key2);
186 }
187 
188 string
189 get_data_for_command_out(command_out_data d)
190 {
191     string data_string;
192     data_string = "";
193     data_string ~= (cast(char*)&d.vers)[0..d.vers.sizeof];
194     data_string ~= (cast(char*)&d.time)[0..d.time.sizeof];
195     data_string ~= (cast(char*)&d.pipe)[0..d.pipe.sizeof];
196     data_string ~= (cast(char*)&d.pos)[0..d.pos.sizeof];
197     final switch (d.vers)
198     {
199         case CommandsOutVersion.Simple:
200             data_string ~= (cast(char*)&d.len)[0..d.len.sizeof];
201             assert(d.attrs.length == d.len);
202             data_string ~= (cast(char*)d.attrs.ptr)[0..ushort.sizeof*d.len];
203             data_string ~= d.output;
204             break;
205 
206         case CommandsOutVersion.Screen:
207             data_string ~= (cast(char*)&d.cols)[0..d.cols.sizeof];
208             data_string ~= (cast(char*)&d.rows)[0..d.rows.sizeof];
209             assert(d.cols*d.rows == d.screen.length);
210             assert(d.cols*d.rows == d.scr_attrs.length);
211             data_string ~= (cast(char*)d.screen.ptr)[0..dchar.sizeof*d.cols*d.rows];
212             data_string ~= (cast(char*)d.scr_attrs.ptr)[0..ushort.sizeof*d.cols*d.rows];
213             break;
214     }
215 
216     return data_string;
217 }
218 
219 void
220 parse_data_for_command_out(string data_string, out command_out_data d)
221 {
222     d.vers = *cast(CommandsOutVersion*)data_string[0..d.vers.sizeof];
223     data_string = data_string[d.vers.sizeof..$];
224     d.time = *cast(ulong*)data_string[0..d.time.sizeof];
225     data_string = data_string[d.time.sizeof..$];
226     d.pipe = *cast(OutPipe*)data_string[0..d.pipe.sizeof];
227     data_string = data_string[d.pipe.sizeof..$];
228     d.pos = *cast(size_t*)data_string[0..d.pos.sizeof];
229     data_string = data_string[d.pos.sizeof..$];
230 
231     final switch (d.vers)
232     {
233         case CommandsOutVersion.Simple:
234             d.len = *cast(int*)data_string[0..d.len.sizeof];
235             data_string = data_string[d.len.sizeof..$];
236             d.attrs = (cast(ushort*)data_string.ptr)[0..d.len];
237             d.output = data_string[ushort.sizeof*d.len..$];
238             break;
239 
240         case CommandsOutVersion.Screen:
241             d.cols = *cast(int*)data_string[0..d.cols.sizeof];
242             data_string = data_string[d.cols.sizeof..$];
243             d.rows = *cast(int*)data_string[0..d.rows.sizeof];
244             data_string = data_string[d.rows.sizeof..$];
245             d.screen = (cast(dchar*)data_string.ptr)[0..d.cols*d.rows];
246             data_string = data_string[dchar.sizeof*d.cols*d.rows..$];
247             d.scr_attrs = (cast(ushort*)data_string.ptr)[0..d.cols*d.rows];
248             assert(data_string.length == ushort.sizeof*d.cols*d.rows);
249             break;
250     }
251 }
252 
253 unittest
254 {
255     command_out_data cmd_data;
256     cmd_data.vers = CommandsOutVersion.Simple;
257     cmd_data.time = 0xABCDEF0123456789;
258     cmd_data.pipe = OutPipe.STDERR;
259     cmd_data.pos = 3458;
260     cmd_data.len = 5;
261     cmd_data.attrs = [65535, 23524, 12235, 43567, 34585];
262     cmd_data.output = "Кра27";
263     string data_string = get_data_for_command_out(cmd_data);
264     command_out_data cmd_data2;
265     parse_data_for_command_out(data_string, cmd_data2);
266     assert(cmd_data.vers == cmd_data2.vers &&
267             cmd_data.time == cmd_data2.time &&
268             cmd_data.pipe == cmd_data2.pipe &&
269             cmd_data.pos == cmd_data2.pos &&
270             cmd_data.len == cmd_data2.len &&
271             cmd_data.attrs == cmd_data2.attrs &&
272             cmd_data.output == cmd_data2.output, 
273             format("%s = %s", cmd_data.output, cmd_data2.output));
274 
275     cmd_data = command_out_data();
276     cmd_data.vers = CommandsOutVersion.Screen;
277     cmd_data.time = 0xABCDEF0123456789;
278     cmd_data.pipe = OutPipe.STDERR;
279     cmd_data.pos = 3458;
280     cmd_data.cols = 3;
281     cmd_data.rows = 2;
282     cmd_data.screen = "Кра276"d.dup();
283     cmd_data.scr_attrs = [65535, 23524, 12235, 43567, 34585, 456];
284     data_string = get_data_for_command_out(cmd_data);
285     cmd_data2 = command_out_data();
286     parse_data_for_command_out(data_string, cmd_data2);
287     assert(cmd_data.vers == cmd_data2.vers &&
288             cmd_data.time == cmd_data2.time &&
289             cmd_data.pipe == cmd_data2.pipe &&
290             cmd_data.pos == cmd_data2.pos &&
291             cmd_data.cols == cmd_data2.cols &&
292             cmd_data.rows == cmd_data2.rows &&
293             cmd_data.screen == cmd_data2.screen &&
294             cmd_data.scr_attrs == cmd_data2.scr_attrs, format("%s = %s", 
295                 cmd_data.screen, cmd_data2.screen));
296 }
297 
298 package ulong
299 find_next_by_key(Dbc cursor, ulong cmd_id, ulong id, ref Dbt key, ref Dbt data)
300 {
301     auto res = cursor.get(&key, &data, DB_SET_RANGE);
302     if (res == DB_NOTFOUND)
303     {
304         //writefln("NOT FOUND, GET LAST");
305         res = cursor.get(&key, &data, DB_FIRST);
306         if (res == DB_NOTFOUND)
307         {
308             //writefln("NOT FOUND");
309             id = 0;
310         }
311     }
312     else if (cmd_id > 0)
313     {
314         //writefln("GOTO PREV");
315         res = cursor.get(&key, &data, DB_NEXT);
316         if (res == DB_NOTFOUND)
317         {
318             //writefln("NOT FOUND");
319             id = 0;
320         }
321     }
322     return id;
323 }
324 
325 package ulong
326 find_prev_by_key(Dbc cursor, ulong id, ref Dbt key, ref Dbt data)
327 {
328     auto res = cursor.get(&key, &data, DB_SET_RANGE);
329     if (res == DB_NOTFOUND)
330     {
331         //writefln("NOT FOUND, GET LAST");
332         res = cursor.get(&key, &data, DB_LAST);
333         if (res == DB_NOTFOUND)
334         {
335             //writefln("NOT FOUND");
336             id = 0;
337         }
338     }
339     else
340     {
341         //writefln("GOTO PREV");
342         res = cursor.get(&key, &data, DB_PREV);
343         if (res == DB_NOTFOUND)
344         {
345             //writefln("NOT FOUND");
346             id = 0;
347         }
348     }
349     return id;
350 }
351 
352 package ulong
353 find_prev_command(Dbc cursor, string cwd, ulong cmd_id,
354         ref Dbt key, ref Dbt data)
355 {
356     ulong id = cmd_id > 0 ? cmd_id : ulong.max;
357     string ks = get_key_for_command(command_key(cwd, id));
358     //writefln("SET RANGE: %s (cwd=%s, id=%X)", ks, cwd, id);
359     key = ks;
360     id = find_prev_by_key(cursor, id, key, data);
361 
362     return id;
363 }
364 
365 package ulong
366 find_next_command(Dbc cursor, string cwd, ulong cmd_id,
367         ref Dbt key, ref Dbt data)
368 {
369     ulong id = cmd_id > 0 ? cmd_id : 1;
370     string ks = get_key_for_command(command_key(cwd, id));
371     //writefln("SET RANGE: %s (cwd=%s, id=%X)", ks, cwd, id);
372     key = ks;
373     id = find_next_by_key(cursor, cmd_id, id, key, data);
374 
375     return id;
376 }
377 
378 package ulong
379 find_prev_command_out(Dbc cursor, string cwd, ulong cmd_id, ulong out_id,
380         ref Dbt key, ref Dbt data)
381 {
382     ulong id = out_id > 0 ? out_id : ulong.max;
383     string ks = get_key_for_command_out(command_out_key(cwd, cmd_id, id));
384     //writefln("SET RANGE: %s (cwd=%s, id=%X)", ks, cwd, id);
385     key = ks;
386     id = find_prev_by_key(cursor, id, key, data);
387 
388     return id;
389 }
390 
391 package ulong
392 find_next_command_out(Dbc cursor, string cwd, ulong cmd_id, ulong out_id,
393         ref Dbt key, ref Dbt data)
394 {
395     ulong id = out_id > 0 ? out_id : 1;
396     string ks = get_key_for_command_out(command_out_key(cwd, cmd_id, id));
397     //writefln("SET RANGE: %s (cwd=%s, id=%X)", ks, cwd, id);
398     key = ks;
399     id = find_next_by_key(cursor, out_id, id, key, data);
400 
401     return id;
402 }
403 
404 private
405 struct TxnPuts
406 {
407    uint txn_id;
408    string[] keys;
409    string[] datas;
410 }
411 
412 package int
413 command_output_put(CMDGlobalState cgs, Dbt *key, Dbt *data)
414 {
415     static TxnPuts txn_puts;
416     auto txn_id = cgs.txn.id();
417     if (txn_id != txn_puts.txn_id)
418     {
419         txn_puts.txn_id = txn_id;
420         txn_puts.keys = [];
421         txn_puts.datas = [];
422     }
423 
424     bool reput;
425     int res;
426 retry:
427     try
428     {
429         if (reput)
430         {
431             txn_puts.txn_id = cgs.txn.id();
432             for (ssize_t i = 0; i < txn_puts.keys.length; i++)
433             {
434                 Dbt key2, data2;
435                 key2 = txn_puts.keys[i];
436                 data2 = txn_puts.datas[i];
437                 cgs.db_command_output.put(cgs.txn, &key2, &data2);
438             }
439         }
440 
441         res = cgs.db_command_output.put(cgs.txn, key, data);
442     }
443     catch (DbDeadlockException exp)
444     {
445         writefln("Oops deadlock, retry");
446         cgs.abort();
447         cgs.recommit();
448         reput = true;
449         goto retry;
450     }
451 
452     string str_key = key.to!(string).idup();
453     if (txn_puts.keys.length > 0 && str_key == txn_puts.keys[$-1])
454     {
455         txn_puts.datas[$-1] ~= data.to!(string).idup();
456     }
457     else
458     {
459         txn_puts.keys ~= str_key;
460         txn_puts.datas ~= data.to!(string).idup();
461     }
462 
463     return res;
464 }