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 }