forked from goldendict/goldendict
-
Notifications
You must be signed in to change notification settings - Fork 0
/
chunkedstorage.cc
166 lines (118 loc) · 3.94 KB
/
chunkedstorage.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/* This file is (c) 2008-2012 Konstantin Isakov <ikm@goldendict.org>
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "chunkedstorage.hh"
#include <zlib.h>
#include <string.h>
namespace ChunkedStorage {
enum
{
ChunkMaxSize = 65536 // Can't be more since it would overflow the address
};
Writer::Writer( File::Class & f ):
file( f ), chunkStarted( false ), bufferUsed( 0 )
{
// Create a sratchpad at the beginning of file. We use it to write chunk
// table if it would fit, in order to save some seek times.
char zero[ 4096 ];
memset( zero, 0, sizeof( zero ) );
scratchPadOffset = file.tell();
scratchPadSize = sizeof( zero );
file.write( zero, sizeof( zero ) );
}
uint32_t Writer::startNewBlock()
{
if ( bufferUsed >= ChunkMaxSize )
{
// Need to flush first.
saveCurrentChunk();
}
chunkStarted = true;
// The address is comprised of the offset within the chunk (in lower
// 16 bits, always fits there since ChunkMaxSize-1 does) and the
// number of the chunk, which is therefore limited to be 65535 max.
return bufferUsed | ( (uint32_t)offsets.size() << 16 );
}
void Writer::addToBlock( void const * data, size_t size )
{
if ( !size )
return;
if ( buffer.size() - bufferUsed < size )
buffer.resize( bufferUsed + size );
memcpy( &buffer.front() + bufferUsed, data, size );
bufferUsed += size;
chunkStarted = false;
}
void Writer::saveCurrentChunk()
{
size_t maxCompressedSize = compressBound( bufferUsed );
if ( bufferCompressed.size() < maxCompressedSize )
bufferCompressed.resize( maxCompressedSize );
unsigned long compressedSize = bufferCompressed.size();
if ( compress( &bufferCompressed.front(), &compressedSize,
&buffer.front(), bufferUsed ) != Z_OK )
throw exFailedToCompressChunk();
offsets.push_back( file.tell() );
file.write( (uint32_t) bufferUsed );
file.write( (uint32_t) compressedSize );
file.write( &bufferCompressed.front(), compressedSize );
bufferUsed = 0;
chunkStarted = false;
}
uint32_t Writer::finish()
{
if ( bufferUsed || chunkStarted )
saveCurrentChunk();
bool useScratchPad = false;
uint32_t savedOffset = 0;
if ( scratchPadSize >= offsets.size() * sizeof( uint32_t ) + sizeof( uint32_t ) )
{
useScratchPad = true;
savedOffset = file.tell();
file.seek( scratchPadOffset );
}
uint32_t offset = file.tell();
file.write( (uint32_t) offsets.size() );
if ( offsets.size() )
file.write( &offsets.front(), offsets.size() * sizeof( uint32_t ) );
if ( useScratchPad )
file.seek( savedOffset );
offsets.clear();
chunkStarted = false;
return offset;
}
Reader::Reader( File::Class & f, uint32_t offset ): file( f )
{
file.seek( offset );
uint32_t size = file.read< uint32_t >();
if ( size == 0 )
return;
offsets.resize( size );
file.read( &offsets.front(), offsets.size() * sizeof( uint32_t ) );
}
char * Reader::getBlock( uint32_t address, vector< char > & chunk )
{
size_t chunkIdx = address >> 16;
if ( chunkIdx >= offsets.size() )
throw exAddressOutOfRange();
// Read and decompress the chunk
{
file.seek( offsets[ chunkIdx ] );
uint32_t uncompressedSize = file.read< uint32_t >();
uint32_t compressedSize = file.read< uint32_t >();
chunk.resize( uncompressedSize );
vector< unsigned char > compressedData( compressedSize );
file.read( &compressedData.front(), compressedData.size() );
unsigned long decompressedLength = chunk.size();
if ( uncompress( (unsigned char *)&chunk.front(),
&decompressedLength,
&compressedData.front(),
compressedData.size() ) != Z_OK ||
decompressedLength != chunk.size() )
throw exFailedToDecompressChunk();
}
size_t offsetInChunk = address & 0xffFF;
if ( offsetInChunk > chunk.size() ) // It can be equal to for 0-sized blocks
throw exAddressOutOfRange();
return &chunk.front() + offsetInChunk;
}
}