/src/libetonyek/src/lib/IWAObjectIndex.cpp
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* |
3 | | * This file is part of the libetonyek project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | */ |
9 | | |
10 | | #include "IWAObjectIndex.h" |
11 | | |
12 | | #include <cassert> |
13 | | |
14 | | #include "IWAMessage.h" |
15 | | #include "IWASnappyStream.h" |
16 | | #include "IWORKTypes.h" |
17 | | |
18 | | #include "IWAParser.h" |
19 | | |
20 | | namespace libetonyek |
21 | | { |
22 | | |
23 | | using boost::optional; |
24 | | |
25 | | using std::deque; |
26 | | using std::make_pair; |
27 | | using std::make_shared; |
28 | | using std::string; |
29 | | |
30 | | IWAObjectIndex::ObjectRecord::ObjectRecord() |
31 | 0 | : m_stream() |
32 | 0 | , m_type(0) |
33 | 0 | , m_headerRange(0, 0) |
34 | 0 | , m_dataRange(0, 0) |
35 | 0 | { |
36 | 0 | } |
37 | | |
38 | | IWAObjectIndex::ObjectRecord::ObjectRecord(const RVNGInputStreamPtr_t &stream, const unsigned type, |
39 | | const long pos, const unsigned long headerLen, const unsigned long dataLen) |
40 | 0 | : m_stream(stream) |
41 | 0 | , m_type(type) |
42 | 0 | , m_headerRange(pos, pos + long(headerLen)) |
43 | 0 | , m_dataRange(m_headerRange.second, m_headerRange.second + long(dataLen)) |
44 | 0 | { |
45 | 0 | } |
46 | | |
47 | | IWAObjectIndex::IWAObjectIndex(const RVNGInputStreamPtr_t &fragments, const RVNGInputStreamPtr_t &package) |
48 | 0 | : m_fragments(fragments) |
49 | 0 | , m_package(package) |
50 | 0 | , m_unparsedFragments() |
51 | 0 | , m_fragmentObjectMap() |
52 | 0 | , m_fileMap() |
53 | 0 | , m_fileColorMap() |
54 | 0 | { |
55 | 0 | } |
56 | | |
57 | | void IWAObjectIndex::parse() |
58 | 0 | { |
59 | 0 | m_unparsedFragments[2] = "Index/Metadata.iwa"; |
60 | 0 | m_fragmentObjectMap[2] = make_pair(2, ObjectRecord()); |
61 | 0 | scanFragment(2); |
62 | 0 | const auto indexIt = m_fragmentObjectMap.find(2); |
63 | 0 | if (indexIt == m_fragmentObjectMap.end() || !indexIt->second.second.m_stream) |
64 | 0 | { |
65 | | // TODO: scan all fragment files |
66 | 0 | ETONYEK_DEBUG_MSG(("IWAObjectIndex::parse: object index is broken, nothing will be parsed\n")); |
67 | 0 | } |
68 | 0 | else |
69 | 0 | { |
70 | 0 | const ObjectRecord &rec = indexIt->second.second; |
71 | 0 | const IWAMessage objectIndex(rec.m_stream, rec.m_dataRange.first, rec.m_dataRange.second); |
72 | 0 | const deque<IWAMessage> &fragments = objectIndex.message(3).repeated(); |
73 | 0 | for (const auto &fragment : fragments) |
74 | 0 | { |
75 | 0 | if (fragment.uint32(1) && (fragment.string(2) || fragment.string(3))) |
76 | 0 | { |
77 | 0 | const unsigned pathIdx = fragment.string(3) ? 3 : 2; |
78 | 0 | m_unparsedFragments[fragment.uint32(1).get()] = "Index/" + fragment.string(pathIdx).get() + ".iwa"; |
79 | 0 | m_fragmentObjectMap[fragment.uint32(1).get()] = make_pair(fragment.uint32(1).get(), ObjectRecord()); |
80 | 0 | } |
81 | 0 | const deque<IWAMessage> &refs = fragment.message(6).repeated(); |
82 | 0 | for (const auto &ref : refs) |
83 | 0 | { |
84 | 0 | if (ref.uint32(1) && ref.uint32(2)) |
85 | 0 | m_fragmentObjectMap[ref.uint32(2).get()] = make_pair(ref.uint32(1).get(), ObjectRecord()); |
86 | 0 | } |
87 | 0 | } |
88 | 0 | const deque<IWAMessage> &files = objectIndex.message(4).repeated(); |
89 | 0 | for (const auto &file : files) |
90 | 0 | { |
91 | 0 | if (file.uint32(1) && m_package) |
92 | 0 | { |
93 | 0 | const string virtualPath(file.string(3) ? ("Data/" + get(file.string(3))) : ""); |
94 | 0 | const string internalPath(file.string(4) ? ("Data/" + get(file.string(4))) : ""); |
95 | 0 | string path; |
96 | 0 | if (!internalPath.empty() && m_package->existsSubStream(internalPath.c_str())) |
97 | 0 | path = internalPath; |
98 | 0 | else if (!virtualPath.empty() && m_package->existsSubStream(virtualPath.c_str())) |
99 | 0 | path = virtualPath; |
100 | 0 | if (!path.empty()) |
101 | 0 | m_fileMap[file.uint32(1).get()] = make_pair(path, RVNGInputStreamPtr_t()); |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | // search the color id map |
106 | 0 | auto replaceId=objectIndex.uint32(1).optional(); |
107 | 0 | boost::optional<unsigned> replaceRef; |
108 | 0 | if (objectIndex.message(10)) replaceRef=objectIndex.message(10).uint32(1).optional(); |
109 | 0 | if (replaceRef) |
110 | 0 | { |
111 | 0 | if (replaceId && get(replaceRef)!=get(replaceId)) |
112 | 0 | { |
113 | | // happens in pages file |
114 | 0 | ETONYEK_DEBUG_MSG(("IWAObjectIndex::parse: replace id=%d is different from replace ref=%d\n", int(get(replaceId)), int(get(replaceRef)))); |
115 | 0 | } |
116 | 0 | scanColorFileMap(get(replaceRef)); |
117 | 0 | } |
118 | 0 | else if (replaceId) |
119 | 0 | scanColorFileMap(get(replaceId)); |
120 | 0 | } |
121 | 0 | } |
122 | | |
123 | | void IWAObjectIndex::queryObject(const unsigned id, unsigned &type, boost::optional<IWAMessage> &msg) const |
124 | 0 | { |
125 | 0 | const auto recIt = m_fragmentObjectMap.find(id); |
126 | 0 | if (recIt == m_fragmentObjectMap.end()) |
127 | 0 | { |
128 | 0 | ETONYEK_DEBUG_MSG(("IWAObjectIndex::queryObject: object %u not found\n", id)); |
129 | 0 | return; |
130 | 0 | } |
131 | 0 | if (!recIt->second.second.m_stream) |
132 | 0 | const_cast<IWAObjectIndex *>(this)->scanFragment(recIt->second.first); |
133 | 0 | if (recIt->second.second.m_stream) |
134 | 0 | { |
135 | 0 | const ObjectRecord &objRecord = recIt->second.second; |
136 | 0 | msg = IWAMessage(objRecord.m_stream, objRecord.m_dataRange.first, objRecord.m_dataRange.second); |
137 | 0 | type = objRecord.m_type; |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | | boost::optional<unsigned> IWAObjectIndex::getObjectType(const unsigned id) const |
142 | 0 | { |
143 | | // improve me |
144 | 0 | boost::optional<IWAMessage> msg; |
145 | 0 | unsigned type=0; // for g++-7 |
146 | 0 | queryObject(id, type, msg); |
147 | 0 | if (!msg) |
148 | 0 | return boost::none; |
149 | 0 | return type; |
150 | 0 | } |
151 | | |
152 | | const RVNGInputStreamPtr_t IWAObjectIndex::queryFile(const unsigned id) const |
153 | 0 | { |
154 | 0 | const auto it = m_fileMap.find(id); |
155 | |
|
156 | 0 | if (it == m_fileMap.end()) |
157 | 0 | { |
158 | 0 | ETONYEK_DEBUG_MSG(("IWAObjectIndex::queryFile: file %u not found\n", id)); |
159 | 0 | return RVNGInputStreamPtr_t(); |
160 | 0 | } |
161 | | |
162 | 0 | if (!it->second.second && m_package) |
163 | 0 | { |
164 | 0 | assert(m_package->existsSubStream(it->second.first.c_str())); // we already checked for its presence |
165 | 0 | it->second.second.reset(m_package->getSubStreamByName(it->second.first.c_str())); |
166 | 0 | } |
167 | |
|
168 | 0 | return it->second.second; |
169 | 0 | } |
170 | | |
171 | | void IWAObjectIndex::scanFragment(const unsigned id) |
172 | 0 | { |
173 | | // scan the fragment file |
174 | 0 | const auto fragmentIt = m_unparsedFragments.find(id); |
175 | 0 | if (fragmentIt != m_unparsedFragments.end()) |
176 | 0 | { |
177 | 0 | const RVNGInputStreamPtr_t stream(m_fragments->getSubStreamByName(fragmentIt->second.c_str())); |
178 | 0 | if (stream) |
179 | 0 | { |
180 | 0 | const auto fragment = make_shared<IWASnappyStream>(stream); |
181 | 0 | scanFragment(fragmentIt->first, fragment); |
182 | 0 | } |
183 | 0 | else |
184 | 0 | { |
185 | 0 | ETONYEK_DEBUG_MSG(("IWAObjectIndex::scanFragment: file %s does not exist\n", fragmentIt->second.c_str())); |
186 | 0 | } |
187 | 0 | m_unparsedFragments.erase(fragmentIt); |
188 | 0 | } |
189 | 0 | } |
190 | | |
191 | | void IWAObjectIndex::scanFragment(const unsigned id, const RVNGInputStreamPtr_t &stream) |
192 | 0 | try |
193 | 0 | { |
194 | 0 | while (!stream->isEnd()) |
195 | 0 | { |
196 | | // scan a single object |
197 | 0 | const uint64_t headerLen = readUVar(stream); |
198 | 0 | const long start = stream->tell(); |
199 | 0 | const uint64_t remaining = getRemainingLength(stream); |
200 | 0 | if (headerLen > remaining) |
201 | 0 | break; |
202 | 0 | const IWAMessage header(stream, headerLen); |
203 | 0 | uint64_t dataLen = 0; |
204 | 0 | optional<unsigned> type; |
205 | 0 | bool ok=true; |
206 | 0 | for (auto const &info : header.message(2)) // go through all data information |
207 | 0 | { |
208 | 0 | if (!info.uint64(3)) |
209 | 0 | { |
210 | 0 | ok=false; |
211 | 0 | break; |
212 | 0 | } |
213 | 0 | if (info.uint64(3).get() > remaining - headerLen - dataLen) |
214 | 0 | { |
215 | 0 | ok=false; |
216 | 0 | break; |
217 | 0 | } |
218 | 0 | dataLen += info.uint64(3).get(); |
219 | 0 | if (!type) type=info.uint32(1).optional(); // normally, all data must define the same type |
220 | 0 | } |
221 | 0 | if (!ok) break; |
222 | 0 | if (header.uint32(1)) |
223 | 0 | { |
224 | 0 | const ObjectRecord rec(stream, get_optional_value_or(type, 0), start, (unsigned long)(headerLen), (unsigned long)(dataLen)); |
225 | 0 | m_fragmentObjectMap[header.uint32(1).get()] = make_pair(id, rec); |
226 | 0 | } |
227 | 0 | if (stream->seek(start + long(headerLen) + long(dataLen), librevenge::RVNG_SEEK_SET) != 0) |
228 | 0 | break; |
229 | 0 | } |
230 | 0 | } |
231 | 0 | catch (...) |
232 | 0 | { |
233 | | // just read as much as possible |
234 | 0 | } |
235 | | |
236 | | boost::optional<IWORKColor> IWAObjectIndex::queryFileColor(unsigned id) const |
237 | 0 | { |
238 | 0 | auto it=m_fileColorMap.find(id); |
239 | 0 | if (it==m_fileColorMap.end()) |
240 | 0 | { |
241 | 0 | ETONYEK_DEBUG_MSG(("IWAObjectIndex::queryFileColor: can not find color for %d\n", int(id))); |
242 | 0 | return boost::none; |
243 | 0 | } |
244 | 0 | return it->second; |
245 | 0 | } |
246 | | |
247 | | void IWAObjectIndex::scanColorFileMap(unsigned id) |
248 | 0 | try |
249 | 0 | { |
250 | 0 | const auto indexIt = m_fragmentObjectMap.find(id); |
251 | 0 | if (indexIt == m_fragmentObjectMap.end() || !indexIt->second.second.m_stream) |
252 | 0 | { |
253 | | // TODO: scan all fragment files |
254 | 0 | ETONYEK_DEBUG_MSG(("IWAObjectIndex::scanColorFileMap: can not find object %d\n", int(id))); |
255 | 0 | return; |
256 | 0 | } |
257 | 0 | const ObjectRecord &rec = indexIt->second.second; |
258 | 0 | const IWAMessage objectIndex(rec.m_stream, rec.m_dataRange.first, rec.m_dataRange.second); |
259 | 0 | for (auto const &corr : objectIndex.message(1).repeated()) |
260 | 0 | { |
261 | 0 | auto ref=IWAParser::readRef(corr, 2); |
262 | 0 | if (!corr.uint32(1) || !ref) |
263 | 0 | { |
264 | 0 | ETONYEK_DEBUG_MSG(("IWAObjectIndex::scanColorFileMap: can not parse some correspondances\n")); |
265 | 0 | continue; |
266 | 0 | } |
267 | 0 | auto color=scanColorFileCorrespondance(*ref); |
268 | 0 | if (color) m_fileColorMap[get(corr.uint32(1))]=get(color); |
269 | 0 | } |
270 | 0 | } |
271 | 0 | catch (...) |
272 | 0 | { |
273 | 0 | } |
274 | | |
275 | | boost::optional<IWORKColor> IWAObjectIndex::scanColorFileCorrespondance(unsigned id) |
276 | 0 | try |
277 | 0 | { |
278 | 0 | const auto indexIt = m_fragmentObjectMap.find(id); |
279 | 0 | if (indexIt == m_fragmentObjectMap.end() || !indexIt->second.second.m_stream) |
280 | 0 | { |
281 | | // TODO: scan all fragment files |
282 | 0 | ETONYEK_DEBUG_MSG(("IWAObjectIndex::scanColorFileCorrespondance: can not find object %d\n", int(id))); |
283 | 0 | return boost::none; |
284 | 0 | } |
285 | 0 | const ObjectRecord &rec = indexIt->second.second; |
286 | 0 | const IWAMessage objectIndex(rec.m_stream, rec.m_dataRange.first, rec.m_dataRange.second); |
287 | 0 | return IWAParser::readColor(objectIndex, 1); |
288 | 0 | } |
289 | 0 | catch (...) |
290 | 0 | { |
291 | 0 | return boost::none; |
292 | 0 | } |
293 | | |
294 | | } |
295 | | |
296 | | /* vim:set shiftwidth=2 softtabstop=2 expandtab: */ |