1 /* This file is part of the KDE project
2 Copyright (C) 2012 Mojtaba Shahi Senobari <mojtaba.shahi3000@gmail.com>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #include "MobiHeaderGenerator.h"
21
22 #include <QDateTime>
23 #include <QChar>
24 #include <QBuffer>
25 #include <QIODevice>
26 #include <QDataStream>
27 #include <QHash>
28
palmDBHeader()29 palmDBHeader::palmDBHeader():
30 attributes(0)
31 , version(0)
32 , lastBackupDate(0)
33 , modificationNumber(0)
34 , appInfoId(0)
35 , sortInfoId(0)
36 , nextRecordIdList(0)
37
38 {
39 }
40
palmDocHeader()41 palmDocHeader::palmDocHeader():
42 compression(2)
43 , unused(0)
44 , maxRecordSize(4096)
45 , encryptionType(0)
46 , unknown(0)
47 {
48 }
49
mobiHeader()50 mobiHeader::mobiHeader():
51 mobiHeaderLength(232)
52 , mobiType(2)
53 , textEncoding(65001)
54 , uniqueId(123456789)
55 , fileVersion(6)
56 , ortographicIndex(0xFFFFFFFF)
57 , inflectionIndex(0xFFFFFFFF)
58 , indexNames(0xFFFFFFFF)
59 , indexkeys(0xFFFFFFFF)
60 , extraIndex0(0xFFFFFFFF)
61 , extraIndex1(0xFFFFFFFF)
62 , extraIndex2(0xFFFFFFFF)
63 , extraIndex3(0xFFFFFFFF)
64 , extraIndex4(0xFFFFFFFF)
65 , extraIndex5(0xFFFFFFFF)
66 , local(9)
67 , inputLanguage(0)
68 , outputLanguage(0)
69 , minversion(6)
70 , huffmanRecordOffset(0)
71 , huffmanRecordCount(0)
72 , huffmanTableOffset(0)
73 , huffmanTableLength(0)
74 , EXTH_Flags(0x50)
75 , unknown1(0)
76 , unknown1_1(0)
77 , unknown1_2(0)
78 , unknown1_3(0)
79 , drmOffset(0xFFFFFFFF)
80 , drmCount(0xFFFFFFFF)
81 , drmSize(0)
82 , drmFlags(0)
83 , unknown2(0)
84 , unknown2_1(0)
85 , firstContentRecordNumber(1)
86 , unknown3(1)
87 , unknown4(1)
88 , unknown5(1)
89 , unknown6(0)
90 , unknown7(0xFFFFFFFF)
91 , unknown8(0)
92 , unknown9(0xFFFFFFFF)
93 , unknown10(0xFFFFFFFF)
94 , extraRecordDataFlags(0)
95 , INDX_recordOffset(0xFFFFFFFF)
96 {
97 }
98
exthHeader()99 exthHeader::exthHeader():
100 exthRecordCount(5)
101 {
102 }
103
MobiHeaderGenerator()104 MobiHeaderGenerator::MobiHeaderGenerator()
105 {
106 }
107
~MobiHeaderGenerator()108 MobiHeaderGenerator::~MobiHeaderGenerator()
109 {
110 }
111
generateMobiHeaders(QHash<QString,QString> metaData,int compressedTextSize,int uncompressedTextSize,QList<int> imagesSize,QList<qint32> textRecordsOffset)112 void MobiHeaderGenerator::generateMobiHeaders(QHash<QString, QString> metaData, int compressedTextSize,
113 int uncompressedTextSize, QList<int> imagesSize,
114 QList<qint32> textRecordsOffset)
115 {
116 m_title = metaData.value("title").toUtf8();
117 if (m_title.isEmpty()) {
118 m_title = QString("Unknown").toUtf8();
119 }
120 m_author = metaData.value("creator").toUtf8();
121 if (m_author.isEmpty()) {
122 m_author = QString("Unknown").toUtf8();
123 }
124
125 m_rawTextSize = compressedTextSize;
126 m_uncompressedTextSize = uncompressedTextSize;
127 m_imgListSize = imagesSize;
128 m_textRecordsOffset = textRecordsOffset;
129
130 m_exthHeader = new exthHeader;
131 m_mobiHeader = new mobiHeader;
132 m_dbHeader = new palmDBHeader;
133 m_docHeader = new palmDocHeader;
134
135 generateEXTH();
136 generatePalmDataBase();
137 generatePalmDocHeader();
138 generateMobiHeader();
139 }
140
generatePalmDataBase()141 void MobiHeaderGenerator::generatePalmDataBase()
142 {
143 m_dbHeader->title = m_title;
144 m_dbHeader->type = "BOOK";
145 m_dbHeader->creator = "MOBI";
146
147 // set creation date
148 // seconds since start of January 1, 1970
149 QDateTime date = QDateTime::currentDateTime();
150
151 qint32 pdTime = date.toTime_t();
152 m_dbHeader->creationDate = pdTime;
153 m_dbHeader->modificationDate = pdTime;
154
155 qint16 recordsCount = qint16(calculateRecordsCount());
156
157 m_dbHeader->uniqueIdSeed = (2 * recordsCount) - 1;
158 m_dbHeader->nextRecordIdList = 0;
159 m_dbHeader->numberOfRecords = recordsCount;
160
161 m_dbHeader->headerLength = (78 + (calculateRecordsCount() * 8)) + 2; // 2 gap zero to make
162 //a multiple of 4
163
164 // I want to set offset but first i need the sizes.
165 int recordId = 0;
166 // record 0
167 m_dbHeader->recordOffset = m_dbHeader->headerLength;
168 m_dbHeader->recordUniqueId = recordId;
169 m_dbHeader->recordsInfo.insert(m_dbHeader->recordOffset, m_dbHeader->recordUniqueId);
170 recordId++;
171 // text record.
172
173 // palmDBHeader length + palmDoc header length (16) + MobiHeader length + EXTH header length
174 // + EHTH header padding + book name (title) length + padding
175 // *(padding are for to make it multiple of four bytes)
176 // + 2052 bytes (I don't know what exactly it is for but i see it in
177 // every file that i have made it by mobi packet creator)
178 // + 1 ( to point the first character of text)
179
180 m_dbHeader->recordOffset = qint32((m_dbHeader->headerLength + 16
181 + m_mobiHeader->mobiHeaderLength
182 + m_exthHeader->headerLength + m_exthHeader->pad
183 + m_title.size() + (4 - (m_title.size() % 4)) + 2052));
184
185
186 m_dbHeader->recordsInfo.insert(m_dbHeader->recordOffset, recordId);
187 qint32 temp = m_dbHeader->recordOffset;
188 recordId++;
189 // "i" ?: I have added a zero byte between block texts, so record offset has gone to forward
190 // after each insert.
191 int i;
192 for (i = 1; i < m_textRecordsOffset.size(); i++) {
193 m_dbHeader->recordOffset = m_textRecordsOffset.at(i) + temp;
194 m_dbHeader->recordOffset += qint32(i);
195 m_dbHeader->recordsInfo.insert(m_dbHeader->recordOffset, recordId);
196 recordId++;
197 }
198 m_dbHeader->recordOffset = (qint32(m_rawTextSize)) + temp + qint32(i - 1);
199 // Each image is just a record and images records can be more that 4096.
200 if (!m_imgListSize.isEmpty()) {
201 m_dbHeader->recordOffset += qint32(1);
202 m_dbHeader->recordUniqueId = recordId;
203 m_dbHeader->recordsInfo.insert(m_dbHeader->recordOffset, m_dbHeader->recordUniqueId);
204 m_dbHeader->recordOffset += qint32(1);
205 recordId++;
206 foreach (int imgSize, m_imgListSize) {
207 // Our image has just one record.
208 m_dbHeader->recordUniqueId = recordId;
209 m_dbHeader->recordsInfo.insert(m_dbHeader->recordOffset, m_dbHeader->recordUniqueId);
210 m_dbHeader->recordOffset += qint32(imgSize);
211 recordId++;
212 }
213 }
214 // FLIS record.
215 m_dbHeader->recordUniqueId = recordId;
216 m_dbHeader->recordsInfo.insert(m_dbHeader->recordOffset, m_dbHeader->recordUniqueId);
217 m_dbHeader->recordOffset += qint32(36); // FLIS record size.
218 recordId++;
219 // FCIS record.
220 m_dbHeader->recordUniqueId = recordId;
221 m_dbHeader->recordsInfo.insert(m_dbHeader->recordOffset, m_dbHeader->recordUniqueId);
222 m_dbHeader->recordOffset += (qint32)44; // FCIS record size.
223 recordId++;
224 // End record.
225 m_dbHeader->recordUniqueId = recordId;
226 m_dbHeader->recordsInfo.insert(m_dbHeader->recordOffset, m_dbHeader->recordUniqueId);
227 }
228
generatePalmDocHeader()229 void MobiHeaderGenerator::generatePalmDocHeader()
230 {
231 m_docHeader->textLength = m_uncompressedTextSize;
232 m_docHeader->pdbrecordCount = qint16(m_textRecordsOffset.size());
233 }
234
generateEXTH()235 void MobiHeaderGenerator::generateEXTH()
236 {
237 m_exthHeader->identifier = "EXTH";
238 // Record type aouthpr 100 <dc:creator>
239 m_exthHeader->exthRecord.insert(100, m_author);
240 // Record type Contributor 108
241 QByteArray contributor = QString("Calligra Author [http://calligra.org]").toUtf8();
242 m_exthHeader->exthRecord.insert(108, contributor);
243 // Record type Source 112
244 QDateTime dateTime = QDateTime::currentDateTime();
245 QDate date = dateTime.date();
246 QByteArray source = date.toString("yyyy-MM-dd").toUtf8() +
247 dateTime.toUTC().time().toString("hh:mm:ss").toUtf8();
248 m_exthHeader->exthRecord.insert(112, source);
249 // 4 bytes identifier, 4 bytes header length,
250 // 4 bytes record count, two record type each one 4 bytes (2 * 4)
251 // two record length (2 * 4)
252 // publisher size, author size
253 m_exthHeader->headerLength = 4 + 4 + 4 + (6 * 2 * 4) + contributor.size() + source.size() +
254 4 + 4 + 4 + m_author.size();
255 // Null bytes to pad the EXTH header to a multiple of four bytes (none if the header is already
256 //a multiple of four). This padding is not included in the EXTH header length.
257 m_exthHeader->pad = 4 - (m_exthHeader->headerLength % 4);
258 }
259
generateMobiHeader()260 void MobiHeaderGenerator::generateMobiHeader()
261 {
262 m_mobiHeader->identifier = "MOBI";
263 if (!m_imgListSize.isEmpty()) {
264 // 2 ( record 0 and first text block)
265 m_mobiHeader->firstNonBookIndex = 2 + m_textRecordsOffset.size();
266 m_mobiHeader->firstImageIndex = 2 + m_textRecordsOffset.size();
267 }
268 else {
269 // Point to FLIS record
270 m_mobiHeader->firstNonBookIndex = calculateRecordsCount() - 3;
271 m_mobiHeader->firstImageIndex = calculateRecordsCount() - 3;
272 }
273
274
275 m_mobiHeader->fullNameOffset = 16 + m_mobiHeader->mobiHeaderLength
276 + m_exthHeader->headerLength + m_exthHeader->pad;
277 m_mobiHeader->fullNameLength = m_title.size();
278 // calculateRecordsCount() - 3 (FLIS, FCIS, end of file)
279 m_mobiHeader->lastContentRecordNumber = calculateRecordsCount() - 4;
280 m_mobiHeader->FLIS_recordNumber = calculateRecordsCount() - 3;
281 m_mobiHeader->FCIS_recordNumber = calculateRecordsCount() - 2;
282 }
283
calculateRecordsCount()284 int MobiHeaderGenerator::calculateRecordsCount()
285 {
286 // the first record is record 0 include MOBI header and EXTH header
287 int recordsCount = 1;
288 recordsCount += m_textRecordsOffset.size();
289 // Images records
290 recordsCount += m_imgListSize.size();
291 // Before first we add a record include two bytes zero.
292 if (!m_imgListSize.isEmpty()) {
293 recordsCount ++;
294 }
295 // FLIS record and FCIS and end of file record
296 recordsCount += 3;
297 return recordsCount;
298 }
299