1 /// Recorder for the archives sread/removed and added to the DART 2 module tagion.dart.Recorder; 3 4 import tagion.hibon.HiBONJSON; 5 6 version (REDBLACKTREE_SAFE_PROBLEM) { 7 /// dmd v2.100+ has problem with rbtree 8 /// Fix: This module hacks the @safe rbtree so it works with dmd v2.100 9 import tagion.std.container.rbtree : RedBlackTree; 10 } 11 else { 12 import std.container.rbtree : RedBlackTree; 13 } 14 import std.algorithm.iteration : map; 15 import std.format; 16 import std.functional : toDelegate; 17 import std.range : empty; 18 import std.range.primitives : ElementType, isInputRange; 19 import std.stdio : File, stdout; 20 import std.traits : FunctionTypeOf; 21 import tagion.basic.Message; 22 import tagion.basic.Types : Buffer; 23 import tagion.basic.Version : ver; 24 import tagion.basic.tagionexceptions : Check; 25 import tagion.crypto.SecureInterfaceNet : HashNet; 26 import tagion.crypto.Types : Fingerprint; 27 import tagion.dart.DARTBasic; 28 import tagion.dart.DARTException : DARTRecorderException; 29 import tagion.hibon.Document : Document; 30 import tagion.hibon.HiBON : HiBON; 31 import tagion.hibon.HiBONRecord : GetLabel, STUB, isHiBONRecord, isStub, label, optional, recordType; 32 import tagion.script.standardnames; 33 34 private alias check = Check!DARTRecorderException; 35 36 /** 37 * Record factory 38 * Used to construct and handle DART recorder 39 */ 40 @safe 41 class RecordFactory { 42 enum order_remove_add = true; 43 const HashNet net; 44 @disable this(); 45 protected this(const HashNet net) { 46 this.net = net; 47 } 48 49 static RecordFactory opCall(const HashNet net) { 50 return new RecordFactory(net); 51 } 52 /** 53 * Creates an empty Recorder 54 * Returns: 55 * new empty recorder 56 */ 57 Recorder recorder() nothrow { 58 return new Recorder; 59 } 60 61 /** 62 * Creates an Recorder from a document 63 * Params: 64 * doc = Documemt formated as recorder 65 * Returns: 66 * new recorder created from doc 67 */ 68 Recorder recorder(const(Document) doc) { 69 return new Recorder(doc); 70 } 71 72 /** 73 * Same as recorder but produce an immutable recorder 74 * Params: doc 75 */ 76 immutable(Recorder) uniqueRecorder(const(Document) doc) const @trusted { 77 const result = new const(Recorder)(doc); 78 return cast(immutable) result; 79 } 80 81 /** 82 * This function should be use with care (rec should only be allocate once) 83 * Params: 84 * rec = is set to null after 85 */ 86 static immutable(Recorder) uniqueRecorder(ref Recorder rec) pure nothrow @trusted { 87 scope (exit) { 88 rec = null; 89 } 90 return cast(immutable) rec; 91 } 92 93 /** 94 * Creates a Recorder base on an existing archive list 95 * Params: 96 * archives = Archive list 97 */ 98 Recorder recorder(Recorder.Archives archives) nothrow { 99 return new Recorder(archives); 100 } 101 102 Recorder recorder(R)(R range, const Archive.Type type = Archive.Type.NONE) if (isInputRange!R) { 103 return new Recorder(range, type); 104 } 105 106 /** 107 * Recorder to recorder (REMOVE, ADD) actions while can be executed by the 108 * modify method 109 */ 110 @safe 111 @recordType("Recorder") 112 class Recorder { 113 /// This will order REMOVE before add 114 115 alias archive_sorted = (a, b) @safe => (a.dart_index < b.dart_index) || ( 116 a.dart_index == b.dart_index) && (a.type < b.type); 117 118 alias Archives = RedBlackTree!(Archive, archive_sorted); 119 package Archives archives; 120 121 import tagion.hibon.HiBONJSON : JSONString; 122 123 mixin JSONString; 124 import tagion.hibon.HiBONRecord : HiBONRecordType; 125 126 mixin HiBONRecordType; 127 /** 128 * Creates a Recorder with an empty archive list 129 * Params: 130 * net = Secure net should be the same as define in the DARTFile class 131 */ 132 private this() pure nothrow { 133 this.archives = new Archives; 134 } 135 136 /** 137 * Creates an Recorder base on an existing archive list 138 * Params: 139 * net = Secure net should be the same as define in the DARTFile class 140 * archives = Archive list 141 */ 142 private this(Archives archives) pure nothrow { 143 this.archives = archives; 144 } 145 146 private this(R)(R range, const Archive.Type type = Archive.Type.NONE) if (isInputRange!R) { 147 archives = new Archives; 148 insert(range, type); 149 } 150 151 private this(Document doc) { 152 153 154 155 .check(isRecord(doc), format("Document is not a %s", ThisType.stringof)); 156 this.archives = new Archives; 157 foreach (e; doc[]) { 158 if (e.key != TYPENAME) { 159 const doc_archive = e.get!Document; 160 auto archive = new Archive(net, doc_archive); 161 archives.insert(archive); 162 } 163 } 164 } 165 166 Recorder dup() pure nothrow { 167 return new Recorder(archives.dup); 168 } 169 170 Archives.ConstRange opSlice() const pure nothrow { 171 return archives[]; 172 } 173 174 Archives.Range opSlice() pure nothrow { 175 return archives[]; 176 } 177 178 Archives.ImmutableRange opSlice() pure nothrow immutable { 179 return archives[]; 180 } 181 182 version (none) void removeOutOfRange(ushort from, ushort to) { //TODO: write unit tests 183 if (from == to) 184 return; 185 immutable ushort to_origin = (to - from) & ushort.max; 186 foreach (archive; archives) { 187 if (archive.type != Archive.Type.REMOVE) { 188 short archiveSector = archive.fingerprint[0] | archive.fingerprint[1]; 189 ushort sector_origin = (archiveSector - from) & ushort.max; 190 if (sector_origin >= to_origin) { 191 archives.removeKey(archive); 192 } 193 } 194 } 195 } 196 197 Recorder changeTypes(const GetType get_type) { 198 import std.algorithm; 199 200 auto result_archives = new Archives; 201 result_archives.insert( 202 archives[].map!(a => new Archive(net, a.filed, get_type(a))) 203 ); 204 return new Recorder(result_archives); 205 } 206 /** 207 * Length of the archives 208 * Returns: number of archives 209 */ 210 size_t length() pure const nothrow { 211 return archives.length; 212 } 213 214 /** 215 * Check if the recorder contains archives 216 * Returns: 217 * true if the recorder is empty 218 */ 219 bool empty() pure const nothrow { 220 return archives.length == 0; 221 } 222 223 /** 224 * Finds an archive with the fingerprint 225 * 226 * Returns: 227 * The archive @ fingerprint and if it dosn't exists then a null reference is returned 228 */ 229 Archive find(const(DARTIndex) dart_index) { 230 if ((dart_index.length !is 0) && (archives !is null)) { 231 auto archive = new Archive(dart_index, Archive.Type.NONE); 232 auto range = archives.equalRange(archive); 233 if ((!range.empty) && (archive.dart_index == range.front.dart_index)) { 234 return range.front; 235 } 236 } 237 return null; 238 } 239 240 Archive find(const(Buffer) fingerprint) { 241 return find(DARTIndex(fingerprint)); 242 } 243 /// 244 unittest { // Check find 245 import tagion.crypto.SecureNet : StdHashNet; 246 247 const hash_net = new StdHashNet; 248 249 auto record_factory = RecordFactory(hash_net); 250 Archive[DARTIndex] set_of_archives; 251 foreach (i; 0 .. 7) { 252 auto hibon = new HiBON; 253 hibon["text"] = format("Some text %d", i); 254 hibon["index"] = i; 255 auto archive = new Archive(hash_net, Document(hibon)); 256 set_of_archives[archive.dart_index] = archive; 257 } 258 259 auto recorder = record_factory.recorder; 260 261 // Check for an empty record 262 assert(recorder.find(set_of_archives.byKey.front) is null); 263 264 // Fill up the record with set_of_archives 265 foreach (a; set_of_archives) { 266 recorder.insert(a); 267 } 268 269 foreach (a; set_of_archives) { 270 auto archive_found = recorder.find(a.dart_index); 271 assert(archive_found); 272 assert(archive_found is a); 273 } 274 275 { // None existing archive 276 auto hibon = new HiBON; 277 hibon["text"] = "Does not exist in the recoder"; 278 auto none_existing_archive = new Archive(hash_net, Document(hibon)); 279 assert(recorder.find(none_existing_archive.dart_index) is null); 280 } 281 } 282 /** 283 * Clear all archives 284 */ 285 void clear() { 286 archives.clear; 287 } 288 289 final const(Archive) insert(const Document doc, const Archive.Type type = Archive.Type.NONE) { 290 auto archive = new Archive(net, doc, type); 291 archives.insert(archive); 292 return archive; 293 } 294 295 final const(Archive) insert(T)(T pack, const Archive.Type type = Archive.Type.NONE) 296 if ((isHiBONRecord!T) && !is(T : const(Recorder))) { 297 return insert(pack.toDoc, type); 298 } 299 300 void insert(Archive archive) //, const Archive.Type type = Archive.Type.NONE) 301 in (!archive.dart_index.empty) 302 do { 303 archives.insert(archive); 304 } 305 306 const(Archive) add(T)(T pack) { 307 return insert(pack, Archive.Type.ADD); 308 } 309 310 const(Archive) remove(T)(T pack) { 311 return insert(pack, Archive.Type.REMOVE); 312 } 313 314 void insert(R)(R range, const Archive.Type type = Archive.Type.NONE) @trusted 315 if ((isInputRange!R) && (is(ElementType!R : const(Document)) || isHiBONRecord!( 316 ElementType!R))) { 317 alias FiledType = ElementType!R; 318 static if (isHiBONRecord!FiledType) { 319 archives.insert(range.map!(a => new Archive(net, a.toDoc, type))); 320 } 321 else { 322 archives.insert(range.map!(a => new Archive(net, a, type))); 323 } 324 } 325 326 final void insert(Recorder r) { 327 archives.insert(r.archives[]); 328 } 329 330 final Archive archive(const Document doc, const Archive.Type type = Archive.Type.NONE) const { 331 return new Archive(net, doc, type); 332 } 333 334 Archive archive(T)(T pack, const Archive.Type type = Archive.Type.NONE) const { 335 return archive(pack.toDoc, type); 336 } 337 338 final void remove(const(DARTIndex) fingerprint) 339 in { 340 assert(fingerprint.length is net.hashSize, 341 format("Length of the fingerprint must be %d but is %d", net.hashSize, fingerprint 342 .length)); 343 } 344 do { 345 auto archive = new Archive(fingerprint, Archive.Type.REMOVE); 346 archives.insert(archive); 347 } 348 349 final void remove(const(Buffer) fingerprint) { 350 remove(DARTIndex(fingerprint)); 351 } 352 353 void dump(File fout = stdout) const { 354 355 foreach (a; archives) { 356 a.dump(fout); 357 } 358 } 359 360 final const(Document) toDoc() const { 361 auto result = new HiBON; 362 uint i; 363 foreach (a; archives) { 364 result[i] = a.toDoc; 365 i++; 366 } 367 result[TYPENAME] = type_name; 368 return Document(result); 369 } 370 371 import std.algorithm.sorting; 372 373 bool checkSorted() pure { 374 return opSlice 375 .isSorted!(archive_sorted); 376 } 377 378 } 379 } 380 381 alias GetType = Archive.Type delegate(const(Archive)) pure nothrow @safe; 382 383 const Add = delegate(const(Archive) a) => Archive.Type.ADD; 384 const Remove = delegate(const(Archive) a) => Archive.Type.REMOVE; 385 const Flip = delegate(const(Archive) a) => -a.type; 386 const Neutral = delegate(const(Archive) a) => a.type; 387 388 /** 389 * Archive element used in the DART Recorder 390 */ 391 @safe class Archive { 392 enum Type : int { 393 NONE = 0, /// NOP DART instruction 394 REMOVE = -1, /// Archive marked as remove instruction 395 ADD = 1, /// Archive marked as add instrunction 396 } 397 398 @label(STUB) @optional const(Fingerprint) fingerprint; /// Stub hash-pointer used in sharding 399 @label(StdNames.archive) @optional const Document filed; /// The actual data strute stored 400 @label(StdNames.archive_type) @optional const(Type) type; /// Acrhive type 401 @label("$i") @optional const(DARTIndex) dart_index; 402 enum archiveLabel = GetLabel!(this.filed).name; 403 enum fingerprintLabel = GetLabel!(this.fingerprint).name; 404 enum typeLabel = GetLabel!(this.type).name; 405 406 mixin JSONString; 407 /* 408 * Construct a 409 * Params: 410 * net = hash function used 411 * doc = document of an archive or filed doc 412 * t = archive type 413 */ 414 this(const HashNet net, const(Document) doc, const Type t = Type.NONE) 415 in { 416 assert(net !is null); 417 } 418 do { 419 420 if(doc.empty) { 421 throw new DARTRecorderException("Document cannot be empty"); 422 } 423 if (doc.isStub) { 424 fingerprint = net.calcHash(doc); 425 dart_index = (doc.hasHashKey) ? net.dartIndex(doc) : cast(DARTIndex)(fingerprint); 426 //dart_index = fingerprint; 427 } 428 else { 429 if (doc.hasMember(archiveLabel)) { 430 filed = doc[archiveLabel].get!Document; 431 } 432 else { 433 filed = doc; 434 } 435 fingerprint = net.calcHash(filed); 436 dart_index = (filed.hasHashKey) ? net.dartIndex(filed) : cast(DARTIndex)(fingerprint); 437 } 438 Type _type = t; 439 if (_type is Type.NONE && doc.hasMember(typeLabel)) { 440 _type = doc[typeLabel].get!Type; 441 } 442 type = _type; 443 444 } 445 446 void dump(File fout = stdout) const { 447 fout.writeln(toString); 448 } 449 450 override string toString() const { 451 const _ptr_addr = (() @trusted => cast(void*) this)(); 452 if (filed.hasHashKey) { 453 return format("Archive # %(%02x%) - %s [%s] %(%02x%)", dart_index, type, _ptr_addr, fingerprint); 454 } 455 456 return format("Archive # %(%02x%) - %s [%s]", dart_index, type, _ptr_addr); 457 } 458 459 version (unittest) { 460 private void changeType(const Type _type) @trusted { 461 auto type_p = cast(Type*)(&type); 462 *type_p = _type; 463 } 464 } 465 /** 466 * Convert archive to a Document 467 * Returns: documnet of the archive 468 */ 469 const(Document) toDoc() const { 470 auto hibon = new HiBON; 471 if (isStub) { 472 hibon[fingerprintLabel] = fingerprint; 473 } 474 else { 475 hibon[archiveLabel] = filed; 476 } 477 if (type !is Type.NONE) { 478 hibon[typeLabel] = type; 479 } 480 return Document(hibon); 481 } 482 483 /** 484 * Define archive by it fingerprint 485 * Use to read or remove an archive with a fingerprint 486 * Params: 487 * fingerprint = hash-key to select the archive 488 * t = type must be either REMOVE or NONE 489 */ 490 private this(const(DARTIndex) dart_index, const Type t = Type.NONE) 491 in (!dart_index.empty) 492 in (t !is Type.ADD) 493 do { 494 type = t; 495 filed = Document(); 496 this.dart_index = dart_index; 497 // fingerprint=null; 498 fingerprint = cast(Fingerprint) dart_index; 499 } 500 501 /** 502 * Checks the translate function get_type result in a remove-type 503 * Params: 504 * get_type = get-type translate function 505 * Returns: true if acrhive is a remove type 506 * 507 */ 508 final bool isRemove(GetType get_type) const { 509 return get_type(this) is Type.REMOVE; 510 } 511 512 /** 513 * Checks if the archive type is a remove-type 514 * Returns: true if type is REMOVE 515 */ 516 final bool isRemove() pure const nothrow { 517 return type is Type.REMOVE; 518 } 519 520 /** 521 * Checks if the archive is an add-type 522 * Returns: true if type is ADD 523 */ 524 final bool isAdd() pure const nothrow { 525 return type is Type.ADD; 526 } 527 528 /** 529 * Check if archive is a stub 530 * Returns: true if archive is a stub 531 */ 532 final bool isStub() pure const nothrow { 533 return filed.empty; 534 } 535 536 /** 537 * Check if the filed archive is of type T 538 * Returns: true if the archive is T 539 */ 540 final bool isRecord(T)() const { 541 return T.isRecord(filed); 542 } 543 544 /** 545 * Generates Document to be store in the BlockFile 546 * Returns: the document to be stored 547 */ 548 const(Document) store() const 549 out (result) { 550 assert(!result.empty, format("Archive is %s type and it must contain data", type)); 551 } 552 do { 553 if (filed.empty) { 554 auto hibon = new HiBON; 555 hibon[fingerprintLabel] = fingerprint; 556 return Document(hibon); 557 } 558 return filed; 559 } 560 561 invariant { 562 assert(!dart_index.empty); 563 } 564 565 } 566 567 /// 568 @safe 569 unittest { // Archive 570 // import std.stdio; 571 import std.format; 572 import std.string : representation; 573 import tagion.dart.DARTFakeNet; 574 import tagion.hibon.HiBONJSON; 575 576 auto net = new DARTFakeNet; 577 auto manufactor = RecordFactory(net); 578 579 static assert(isHiBONRecord!Archive); 580 Document filed_doc; // This is the data which is filed in the DART 581 { 582 auto hibon = new HiBON; 583 hibon["#text"] = "Some text"; 584 filed_doc = Document(hibon); 585 } 586 immutable filed_doc_fingerprint = net.calcHash(filed_doc); 587 immutable filed_doc_dart_index = net.dartIndex(filed_doc); 588 589 Archive a; 590 { // Simple archive 591 a = new Archive(net, filed_doc); 592 assert(a.fingerprint == filed_doc_fingerprint); 593 assert(a.dart_index == filed_doc_dart_index); 594 assert(a.filed == filed_doc); 595 assert(a.type is Archive.Type.NONE); 596 597 const archived_doc = a.toDoc; 598 assert(archived_doc[Archive.archiveLabel].get!Document == filed_doc); 599 const result_a = new Archive(net, archived_doc); 600 assert(result_a.fingerprint == a.fingerprint); 601 assert(a.dart_index == filed_doc_dart_index); 602 assert(result_a.filed == a.filed); 603 assert(result_a.type == a.type); 604 assert(result_a.store == filed_doc); 605 606 } 607 608 a.changeType(Archive.Type.ADD); 609 { // Simple archive with ADD/REMOVE Type 610 // a=new Archive(net, filed_doc); 611 assert(a.fingerprint == filed_doc_fingerprint); 612 const archived_doc = a.toDoc; 613 614 { // Same type 615 const result_a = new Archive(net, archived_doc); 616 assert(result_a.fingerprint == a.fingerprint); 617 assert(result_a.filed == a.filed); 618 assert(result_a.type == a.type); 619 assert(result_a.store == filed_doc); 620 } 621 622 } 623 624 } 625 626 @safe 627 unittest { /// RecordFactory.Recorder.insert range 628 import std.algorithm.comparison : equal; 629 import std.algorithm.sorting : sort; 630 import std.array : array; 631 import std.range : chain, iota; 632 import std.stdio : writefln; 633 import tagion.crypto.SecureNet; 634 import tagion.hibon.HiBONRecord; 635 636 const net = new StdHashNet; 637 auto manufactor = RecordFactory(net); 638 static struct Filed { 639 int x; 640 mixin HiBONRecord!( 641 q{ 642 this(int x) { 643 this.x = x; 644 } 645 }); 646 } 647 648 auto range_filed = iota(5).map!(i => Filed(i)); 649 650 auto recorder = manufactor.recorder(range_filed); 651 652 enum recorder_sorted = (RecordFactory.Recorder rec) @safe => rec[] 653 .map!(a => Filed(a.filed)) 654 .array 655 .sort!((a, b) => a.x < b.x); 656 657 { // Check the content of 658 assert(equal(range_filed, recorder_sorted(recorder))); 659 } 660 661 { // Insert range of HiBON's 662 auto range_filed_insert = iota(5, 10).map!(i => Filed(i)); 663 recorder.insert(range_filed_insert); 664 assert(equal( 665 chain(range_filed, range_filed_insert), 666 recorder_sorted(recorder))); 667 } 668 669 { /// Insert recorder to recorder 670 671 auto recorder_base = manufactor.recorder(iota(3, 6).map!(i => Filed(i))); 672 auto recorder_insert = manufactor.recorder(iota(0, 3).map!(i => Filed(i))); 673 recorder_base.insert(recorder_insert); 674 assert(equal( 675 recorder_sorted(recorder_base), 676 iota(0, 6).map!(i => Filed(i)))); 677 } 678 679 } 680 681 @safe 682 unittest { 683 immutable(ulong[]) table = [ 684 // RIM 2 test (rim=2) 685 0x20_21_10_30_40_50_80_90, 686 0x20_21_11_30_40_50_80_90, 687 0x20_21_12_30_40_50_80_90, 688 0x20_21_0a_30_40_50_80_90, // Insert before in rim 2 689 690 ]; 691 692 import std.array : array; 693 import tagion.dart.DARTFakeNet; 694 695 const net = new DARTFakeNet; 696 auto manufactor = RecordFactory(net); 697 { /// Remove must come before two archive with the same fingerprint 698 699 const doc = DARTFakeNet.fake_doc(0x1234_5678_0000_0000); 700 auto rec = manufactor.recorder; 701 rec.insert(doc, Archive.Type.ADD); 702 rec.insert(doc, Archive.Type.REMOVE); 703 704 import std.stdio; 705 706 writeln(" ------------------------------ "); 707 //rec.dump; 708 709 const archs = table.map!(t => DARTFakeNet.fake_doc(t)).array; 710 711 rec.insert(archs[0], Archive.Type.ADD); 712 rec.insert(archs[1], Archive.Type.REMOVE); 713 rec.insert(archs[3], Archive.Type.ADD); 714 rec.insert(archs[1], Archive.Type.ADD); 715 rec.insert(archs[1], Archive.Type.ADD); 716 rec.insert(archs[3], Archive.Type.REMOVE); 717 rec.insert(archs[2], Archive.Type.ADD); 718 719 //rec.//dump; 720 assert(rec.checkSorted); 721 722 } 723 }