1 /// \file HashChainFileStorage.d
2 module tagion.hashchain.HashChainFileStorage;
3 
4 import std.algorithm : filter, map;
5 import std.array : array;
6 import std.file;
7 import std.path;
8 import tagion.basic.Types : Buffer, FileExtension;
9 import tagion.crypto.SecureInterfaceNet : HashNet;
10 import tagion.crypto.SecureNet : StdHashNet;
11 import tagion.crypto.Types : Fingerprint;
12 import tagion.hashchain.HashChainStorage : HashChainStorage;
13 import tagion.hibon.HiBONFile : fread, fwrite;
14 import tagion.utils.Miscellaneous : decode;
15 /** @brief File contains class HashChainFileStorage
16  */
17 
18 /**
19  * \class HashChainFileStorage
20  * Implementation of hash chain storage, based on file system
21  */
22 @safe class HashChainFileStorage(Block) : HashChainStorage!Block {
23     protected {
24         /** Path to local folder where chain files are stored */
25         string folder_path;
26 
27         /** Hash net */
28         const HashNet net;
29     }
30 
31     this(string folder_path, const HashNet net) {
32         this.folder_path = folder_path;
33         this.net = net;
34 
35         if (!exists(this.folder_path)) {
36             mkdirRecurse(this.folder_path);
37         }
38     }
39 
40     /** Writes given block to file
41      *      @param block - block to write
42      */
43     void write(const(Block) block) {
44         fwrite(makePath(block.getHash), block.toHiBON);
45     }
46 
47     /** Reads file with given fingerprint and creates block from read data
48      *      @param fingerprint - fingerprint of block to read
49      *      \return chain block, or null if such block file doesn't exist
50      */
51     Block read(const Fingerprint fingerprint) {
52         try {
53             auto doc = fread(makePath(fingerprint));
54             // TODO: bad decision, redesign to have automatic set hash from only doc
55             return new Block(doc, net);
56         }
57         catch (Exception e) {
58             return null;
59         }
60     }
61 
62     /** Finds block that satisfies given predicate 
63      *      @param predicate - predicate for block
64      *      \return block if search was successfull, null - if such block doesn't exist
65      */
66     Block find(bool delegate(Block) @safe predicate) {
67         auto hashes = getHashes;
68         foreach (hash; hashes) {
69             auto block = read(hash);
70             if (predicate(block)) {
71                 return block;
72             }
73         }
74         return null;
75     }
76 
77     /** Collects all block filenames in chain folder 
78      *      \return array of block filenames in this folder
79      */
80     Fingerprint[] getHashes() @trusted {
81         enum BLOCK_FILENAME_LEN = StdHashNet.HASH_SIZE * 2;
82 
83         return folder_path.dirEntries(SpanMode.shallow)
84             .filter!(f => f.isFile())
85             .map!(f => baseName(f))
86             .filter!(f => f.extension == getExtension)
87             .filter!(f => f.stripExtension.length == BLOCK_FILENAME_LEN)
88             .map!(f => f.stripExtension)
89             .map!(f => Fingerprint(f.decode))
90             .array;
91     }
92 
93     private {
94         static FileExtension getExtension() {
95             import tagion.epochain.EpochChainBlock : EpochChainBlock;
96             import tagion.recorderchain.RecorderChainBlock : RecorderChainBlock;
97 
98             static if (is(Block == RecorderChainBlock)) {
99                 return FileExtension.hibon;
100             }
101             static if (is(Block == EpochChainBlock)) {
102                 return FileExtension.epochdumpblock;
103             }
104 
105             version (unittest) {
106                 // Default extension for using in unittest
107                 return FileExtension.hibon;
108             }
109             assert(false, "Unknown block type instantiated for HashChainFileStorage");
110         }
111 
112         /** Creates path to block with given fingerprint
113          *      @param fingerprint - fingerprint of block to make path
114          *      \return path to block with given fingerprint
115          */
116         string makePath(const Fingerprint fingerprint) {
117             import std.format;
118             return buildPath(
119                     folder_path,
120                     format!"%(%02x%)"(fingerprint).setExtension(getExtension));
121         }
122     }
123 }