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 }