File indexing completed on 2024-04-14 05:41:03
0001 /** 0002 * SPDX-FileCopyrightText: 2021 Sebastian Engel <kde@sebastianengel.eu> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "weaver.h" 0008 0009 0010 // TODO include libBasket instead of hardcoded link 0011 #include "../../src/archive.h" 0012 0013 #include <KLocalizedString> 0014 #include <QDebug> 0015 #include <QDir> 0016 #include <QFile> 0017 #include <QFileInfo> 0018 #include <QMimeDatabase> 0019 #include <QMimeType> 0020 #include <QXmlStreamReader> 0021 0022 void translateErrorCode(const Archive::IOErrorCode code); 0023 0024 Weaver::Weaver(QCommandLineParser *parser, 0025 const QCommandLineOption &mode_weave, 0026 const QCommandLineOption &mode_unweave, 0027 const QCommandLineOption &output, 0028 const QCommandLineOption &basename, 0029 const QCommandLineOption &previewImg, 0030 const QCommandLineOption &force) 0031 : m_parser(parser) 0032 , m_weave(mode_weave) 0033 , m_unweave(mode_unweave) 0034 , m_output(output) 0035 , m_basename(basename) 0036 , m_previewImg(previewImg) 0037 , m_force(force) 0038 , m_preview(QString()) 0039 { 0040 } 0041 0042 int Weaver::runMain() 0043 { 0044 if (!m_parser->isSet(m_unweave) && !m_parser->isSet(m_weave)) { 0045 qCritical().noquote() << i18n("You need to provide at least one --weave/-w or --unweave/-u option"); 0046 return 1; 0047 } 0048 0049 if (m_parser->isSet(m_unweave) && m_parser->isSet(m_weave)) { 0050 qCritical().noquote() << i18n("You cannot use --weave/-w and --unweave/-u options in conjunction."); 0051 return 1; 0052 } 0053 0054 if (m_parser->values(m_unweave).size() > 1 || m_parser->values(m_weave).size() > 1) { 0055 qWarning().noquote() << i18n( 0056 "Multiple --weave/-w or --unweave/-u input options found. All but the first input" 0057 " are going to be ignored."); 0058 } 0059 0060 bool ret = true; 0061 0062 if (m_parser->isSet(m_weave)) { 0063 ret = weave(); 0064 } else if (m_parser->isSet(m_unweave)) { 0065 ret = unweave(); 0066 } 0067 0068 if (!ret) { 0069 return 1; 0070 } 0071 0072 return 0; 0073 } 0074 0075 bool Weaver::unweave() 0076 { 0077 m_in = m_parser->value(m_unweave); 0078 0079 if (!isBasketFile(m_in)) { 0080 qCritical().noquote() << i18n("The source seems to be an invalid .baskets file"); 0081 return false; 0082 } 0083 0084 if (m_parser->isSet(m_output)) { 0085 m_out = m_parser->value(m_output); 0086 if (!QFileInfo::exists(m_out)) { 0087 qCritical().noquote() << i18n("Output directory does not exist."); 0088 return false; 0089 } 0090 } else { 0091 m_out = QFileInfo(m_in).absoluteDir().path(); 0092 } 0093 0094 QString destination = m_out + QDir::separator(); 0095 if (m_parser->isSet(m_basename)) { 0096 destination += m_parser->value(m_basename); 0097 } else { 0098 destination += QFileInfo(m_in).baseName() + "_baskets"; 0099 } 0100 0101 Archive::IOErrorCode errorCode = Archive::extractArchive(m_in, destination, !m_parser->isSet(m_force)); 0102 0103 translateErrorCode(errorCode); 0104 0105 return errorCode == Archive::IOErrorCode::NoError; 0106 } 0107 0108 bool Weaver::weave() 0109 { 0110 m_in = m_parser->value(m_weave); 0111 m_preview = m_parser->value(m_previewImg); 0112 0113 if (!isBasketSourceValid(m_in)) { 0114 qCritical().noquote() << i18n("The source seems to be invalid."); 0115 if (!m_parser->isSet(m_force)) { 0116 return false; 0117 } 0118 } 0119 0120 m_preview = m_parser->value(m_previewImg); 0121 if (!isPreviewValid(m_preview)) { 0122 m_preview = QString(); 0123 } 0124 0125 if (m_parser->isSet(m_output)) { 0126 m_out = m_parser->value(m_output); 0127 if (!QFileInfo::exists(m_out)) { 0128 qCritical().noquote() << i18n("Output directory does not exist."); 0129 return false; 0130 } 0131 } else { 0132 QDir inputPath(m_in); 0133 inputPath.cdUp(); 0134 m_out = inputPath.absolutePath(); 0135 } 0136 0137 QString destination = m_out + QDir::separator(); 0138 if (m_parser->isSet(m_basename)) { 0139 destination += m_parser->value(m_basename); 0140 } else { 0141 destination += QFileInfo(m_in).baseName(); 0142 } 0143 0144 const QString extenstion = QStringLiteral(".baskets"); 0145 if (!destination.endsWith(extenstion)) { 0146 destination += extenstion; 0147 } 0148 0149 Archive::IOErrorCode errorCode = Archive::createArchiveFromSource(m_in, m_preview, destination, !m_parser->isSet(m_force)); 0150 0151 translateErrorCode(errorCode); 0152 0153 return errorCode == Archive::IOErrorCode::NoError; 0154 } 0155 0156 bool Weaver::isBasketSourceValid(const QString &basketsDirectory) 0157 { 0158 QFileInfo dirInfo(basketsDirectory); 0159 if (!dirInfo.isDir() || !dirInfo.exists()) { 0160 return false; 0161 } 0162 0163 // test the existence of /baskets/baskets.xml 0164 const QString basketTreePath = basketsDirectory + QDir::separator() + "baskets" + QDir::separator() + "baskets.xml"; 0165 if (!QFileInfo::exists(basketTreePath)) { 0166 return false; 0167 } 0168 0169 QStringList containedBaskets; 0170 QFile basketTree(basketTreePath); 0171 if (basketTree.open(QIODevice::ReadOnly)) { 0172 QXmlStreamReader xml(&basketTree); 0173 0174 // find the beginning of the basketTree element 0175 while (!xml.atEnd()) { 0176 xml.readNextStartElement(); 0177 if (xml.name() == QStringLiteral("basketTree")) { 0178 break; 0179 } 0180 } 0181 if (xml.atEnd()) { 0182 basketTree.close(); 0183 return false; 0184 } 0185 // collect all referenced baskets 0186 while (!xml.atEnd()) { 0187 xml.readNextStartElement(); 0188 if (xml.name() == QStringLiteral("basket")) { 0189 if (xml.attributes().hasAttribute(QStringLiteral("folderName"))) { 0190 containedBaskets.append(xml.attributes().value(QStringLiteral("folderName")).toString()); 0191 xml.skipCurrentElement(); 0192 } else { 0193 basketTree.close(); 0194 return false; 0195 } 0196 } else { 0197 break; 0198 } 0199 } 0200 } 0201 basketTree.close(); 0202 0203 // test whether the referenced subdirectories exist 0204 for (const QString &bskt : containedBaskets) { 0205 const QString bsktPath = basketsDirectory + QDir::separator() + "baskets" + QDir::separator() + bskt; 0206 if (!QFileInfo::exists(bsktPath)) { 0207 return false; 0208 } 0209 } 0210 0211 return true; 0212 } 0213 0214 bool Weaver::isBasketFile(const QString &basketsFile) 0215 { 0216 QFile file(basketsFile); 0217 0218 if (!file.exists()) { 0219 return false; 0220 } 0221 0222 if (file.open(QIODevice::ReadOnly)) { 0223 QTextStream stream(&file); 0224 QString line = stream.readLine(); 0225 stream.setCodec("ISO-8859-1"); 0226 if (line != QStringLiteral("BasKetNP:archive")) { 0227 file.close(); 0228 return false; 0229 } 0230 0231 while (!stream.atEnd()) { 0232 line = stream.readLine(); 0233 int index = line.indexOf(':'); 0234 QString key; 0235 QString value; 0236 if (index >= 0) { 0237 key = line.left(index); 0238 value = line.right(line.length() - index - 1); 0239 } else { 0240 key = line; 0241 value = QString(); 0242 } 0243 // only test existence of keywords 0244 if (key == QStringLiteral("version") || key == QStringLiteral("read-compatible") || 0245 key == QStringLiteral("write-compatible")) { 0246 continue; 0247 } 0248 // test for existence, then skip block of given size 0249 if (key == QStringLiteral("preview*")) { 0250 bool ok = false; 0251 const qint64 size = value.toULong(&ok); 0252 if (!ok) { 0253 file.close(); 0254 return false; 0255 } 0256 stream.seek(stream.pos() + size); 0257 } 0258 // test for existence, then skip block of given size 0259 else if (key == QStringLiteral("archive*")) { 0260 bool ok = false; 0261 qint64 size = value.toULong(&ok); 0262 if (!ok) { 0263 file.close(); 0264 return false; 0265 } 0266 stream.seek(stream.pos() + size); 0267 } 0268 // test unknown embedded file, then skip block of given size 0269 else if (key.endsWith('*')) { 0270 bool ok = false; 0271 qint64 size = value.toULong(&ok); 0272 if (!ok) { 0273 file.close(); 0274 return false; 0275 } 0276 stream.seek(stream.pos() + size); 0277 } 0278 } 0279 file.close(); 0280 0281 } else { 0282 qCritical().noquote() << i18n("Could not open file."); 0283 return false; 0284 } 0285 0286 return true; 0287 } 0288 0289 bool Weaver::isPreviewValid(const QString &previewFile) 0290 { 0291 if (previewFile.isEmpty() || !QFileInfo::exists(previewFile)) { 0292 return false; 0293 } 0294 0295 // is the given file a png? 0296 QMimeDatabase db; 0297 QMimeType mime = db.mimeTypeForFile(previewFile); 0298 0299 return mime.inherits(QStringLiteral("image/png")); 0300 } 0301 0302 void translateErrorCode(const Archive::IOErrorCode code) 0303 { 0304 switch (code) { 0305 case Archive::IOErrorCode::FailedToOpenResource: 0306 qCritical().noquote() << i18n("Failed to open a file resource."); 0307 break; 0308 case Archive::IOErrorCode::NotABasketArchive: 0309 qCritical().noquote() << i18n("This file is not a basket archive."); 0310 break; 0311 case Archive::IOErrorCode::CorruptedBasketArchive: 0312 qCritical().noquote() << i18n("This file is corrupted. It can not be opened."); 0313 break; 0314 case Archive::IOErrorCode::DestinationExists: 0315 qCritical().noquote() << i18n("The destination path already exists."); 0316 break; 0317 case Archive::IOErrorCode::IncompatibleBasketVersion: 0318 qCritical().noquote() << i18n("This file supplied file format is not supported"); 0319 break; 0320 case Archive::IOErrorCode::PossiblyCompatibleBasketVersion: 0321 qWarning().noquote() << i18n("This file was created with a more recent version of BasKet Note Pads. It might not be fully supported"); 0322 [[fallthrough]]; 0323 case Archive::IOErrorCode::NoError: 0324 break; 0325 } 0326 }