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