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 }