File indexing completed on 2024-05-05 05:40:38
0001 /*************************************************************************** 0002 * Copyright (C) 2021 by Renaud Guezennec * 0003 * http://www.rolisteam.org/contact * 0004 * * 0005 * This software is free software; you can redistribute it and/or modify * 0006 * it under the terms of the GNU General Public License as published by * 0007 * the Free Software Foundation; either version 2 of the License, or * 0008 * (at your option) any later version. * 0009 * * 0010 * This program is distributed in the hope that it will be useful, * 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0013 * GNU General Public License for more details. * 0014 * * 0015 * You should have received a copy of the GNU General Public License * 0016 * along with this program; if not, write to the * 0017 * Free Software Foundation, Inc., * 0018 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * 0019 ***************************************************************************/ 0020 #include "worker/fileserializer.h" 0021 0022 #include "data/campaign.h" 0023 #include "data/campaignmanager.h" 0024 #include "data/media.h" 0025 #include "model/dicealiasmodel.h" 0026 #include "model/nonplayablecharactermodel.h" 0027 #include "utils/iohelper.h" 0028 #include "worker/iohelper.h" 0029 0030 #include <QDir> 0031 #include <QDirIterator> 0032 #include <QFile> 0033 #include <QFileInfo> 0034 #include <QJsonArray> 0035 #include <QJsonDocument> 0036 #include <QtConcurrent> 0037 0038 namespace campaign 0039 { 0040 0041 bool FileSerializer::createCampaignDirectory(const QString& campaign) 0042 { 0043 bool res= false; 0044 QDir dir(campaign); 0045 0046 res= dir.mkdir(campaign::MEDIA_ROOT); 0047 res&= dir.mkdir(campaign::STATE_ROOT); 0048 res&= dir.mkdir(campaign::TRASH_FOLDER); 0049 res&= dir.mkdir(campaign::CHARACTER_ROOT); 0050 0051 return res; 0052 } 0053 0054 Core::MediaType FileSerializer::typeFromExtention(const QString& filename) 0055 { 0056 static QSet<QString> image{"png", "jpg", "jpeg", "bmp", "gif", "tiff"}; 0057 static QSet<QString> pdf{"pdf"}; 0058 static QSet<QString> mindmap{"rmap"}; 0059 static QSet<QString> vmap{"vmap"}; 0060 static QSet<QString> text{"txt", "md", "tst", "json"}; 0061 static QSet<QString> html{"rweb"}; 0062 static QSet<QString> token{"rtok"}; 0063 static QSet<QString> playlist{"m3u"}; 0064 static QSet<QString> audio{"mp3", "ogg", "mpc", "wav"}; 0065 0066 QHash<Core::MediaType, QSet<QString>> hash{ 0067 {Core::MediaType::ImageFile, image}, {Core::MediaType::PdfFile, pdf}, 0068 {Core::MediaType::MindmapFile, mindmap}, {Core::MediaType::MapFile, vmap}, 0069 {Core::MediaType::TextFile, text}, {Core::MediaType::WebpageFile, html}, 0070 {Core::MediaType::TokenFile, token}, {Core::MediaType::PlayListFile, playlist}, 0071 {Core::MediaType::AudioFile, audio}}; 0072 0073 QFileInfo info(filename); 0074 auto ext= info.suffix(); 0075 0076 auto keys= hash.keys(); 0077 for(auto type : keys) 0078 { 0079 auto set= hash[type]; 0080 if(set.contains(ext)) 0081 return type; 0082 } 0083 0084 return Core::MediaType::Unknown; 0085 } 0086 0087 CampaignInfo FileSerializer::readCampaignDirectory(const QString& directory) 0088 { 0089 CampaignInfo info; 0090 info.status= false; 0091 if(directory.isEmpty()) 0092 return info; 0093 0094 QDir dir(directory); 0095 if(!dir.exists() || !dir.isReadable()) 0096 info.errors << QObject::tr("Error: %1 does not exist or it is not readable.").arg(directory); 0097 0098 if(dir.isEmpty()) 0099 { 0100 info.status= true; 0101 createCampaignDirectory(directory); 0102 return info; 0103 } 0104 0105 // read model: data.json 0106 bool ok; 0107 info.asset= IOHelper::loadJsonFileIntoObject(QStringLiteral("%1/%2").arg(directory, campaign::MODEL_FILE), ok); 0108 0109 // read themes.json 0110 info.theme= IOHelper::loadJsonFileIntoObject(QStringLiteral("%1/%2").arg(directory, campaign::THEME_FILE), ok); 0111 0112 // read dices.json 0113 info.dices= IOHelper::loadJsonFileIntoArray(QStringLiteral("%1/%2").arg(directory, campaign::DICE_ALIAS_MODEL), ok); 0114 0115 // read states.json 0116 info.states= IOHelper::loadJsonFileIntoArray(QStringLiteral("%1/%2").arg(directory, campaign::STATE_MODEL), ok); 0117 0118 // read npcs.json 0119 info.npcs= IOHelper::loadJsonFileIntoArray(QStringLiteral("%1/%2").arg(directory, campaign::CHARACTER_MODEL), ok); 0120 0121 // read 0122 auto assetRoot= QString("%1/%2").arg(directory, campaign::MEDIA_ROOT); 0123 auto array= info.asset[Core::JsonKey::JSON_MEDIAS].toArray(); 0124 0125 QSet<QString> managedFiles; 0126 for(auto const& item : qAsConst(array)) 0127 { 0128 auto asset= item.toObject(); 0129 auto path= asset[Core::JsonKey::JSON_MEDIA_PATH].toString(); 0130 auto filepath= QStringLiteral("%1/%2").arg(assetRoot, path); 0131 QFileInfo fileInfo(filepath); 0132 managedFiles << filepath; 0133 if(!fileInfo.exists()) 0134 { 0135 info.missingFiles << path; 0136 } 0137 } 0138 0139 QDirIterator it(assetRoot, QDir::Files, QDirIterator::Subdirectories); 0140 while(it.hasNext()) 0141 { 0142 auto path= it.next(); 0143 if(!managedFiles.contains(path)) 0144 { 0145 info.unmanagedFiles << path; 0146 } 0147 } 0148 0149 // check integrity of data.json and files 0150 info.status= true; 0151 0152 return info; 0153 } 0154 0155 QJsonObject FileSerializer::campaignToObject(Campaign* campaign) 0156 { 0157 QJsonObject root; 0158 auto const& data= campaign->medias(); 0159 QJsonArray array; 0160 for(auto const& media : data) 0161 { 0162 QJsonObject obj; 0163 obj[Core::JsonKey::JSON_MEDIA_PATH] 0164 = utils::IOHelper::absoluteToRelative(media->path(), campaign->directory(Campaign::Place::MEDIA_ROOT)); 0165 obj[Core::JsonKey::JSON_MEDIA_CREATIONTIME]= media->addedTime().toString(Qt::ISODate); 0166 obj[Core::JsonKey::JSON_MEDIA_ID]= media->id(); 0167 array.append(obj); 0168 } 0169 root[Core::JsonKey::JSON_MEDIAS]= array; 0170 root[Core::JsonKey::JSON_NAME]= campaign->name(); 0171 return root; 0172 } 0173 0174 QJsonArray FileSerializer::npcToArray(const std::vector<std::unique_ptr<campaign::NonPlayableCharacter>>& vec, 0175 const QString& destination) 0176 { 0177 QJsonArray array; 0178 std::transform(std::begin(vec), std::end(vec), std::back_inserter(array), 0179 [destination](const std::unique_ptr<campaign::NonPlayableCharacter>& npc) { 0180 return IOHelper::npcToJsonObject(npc.get(), destination); 0181 }); 0182 return array; 0183 } 0184 0185 QJsonArray FileSerializer::statesToArray(const std::vector<std::unique_ptr<CharacterState>>& vec, 0186 const QString& destination) 0187 { 0188 QJsonArray array; 0189 std::transform(std::begin(vec), std::end(vec), std::back_inserter(array), 0190 [destination](const std::unique_ptr<CharacterState>& alias) { 0191 return IOHelper::stateToJSonObject(alias.get(), destination); 0192 }); 0193 return array; 0194 } 0195 0196 QJsonArray FileSerializer::dicesToArray(const std::vector<std::unique_ptr<DiceAlias>>& vec) 0197 { 0198 QJsonArray array; 0199 std::transform( 0200 std::begin(vec), std::end(vec), std::back_inserter(array), 0201 [](const std::unique_ptr<DiceAlias>& alias) { return IOHelper::diceAliasToJSonObject(alias.get()); }); 0202 return array; 0203 } 0204 0205 void FileSerializer::writeStatesIntoCampaign(const QString& destination, const QJsonArray& array) 0206 { 0207 QtConcurrent::run([destination, array]() { 0208 IOHelper::writeJsonArrayIntoFile(QString("%1/%2").arg(destination, campaign::STATE_MODEL), array); 0209 }); 0210 } 0211 0212 void FileSerializer::writeNpcIntoCampaign(const QString& destination, const QJsonArray& array) 0213 { 0214 QtConcurrent::run([destination, array]() { 0215 IOHelper::writeJsonArrayIntoFile(QString("%1/%2").arg(destination, campaign::CHARACTER_MODEL), array); 0216 }); 0217 } 0218 0219 void FileSerializer::writeDiceAliasIntoCampaign(const QString& destination, const QJsonArray& array) 0220 { 0221 QtConcurrent::run([destination, array]() { 0222 IOHelper::writeJsonArrayIntoFile(QString("%1/%2").arg(destination, campaign::DICE_ALIAS_MODEL), array); 0223 }); 0224 } 0225 0226 void FileSerializer::writeCampaignInfo(const QString& destination, const QJsonObject& object) 0227 { 0228 QtConcurrent::run([destination, object]() { 0229 IOHelper::writeJsonObjectIntoFile(QString("%1/%2").arg(destination, campaign::MODEL_FILE), object); 0230 }); 0231 } 0232 0233 QFuture<bool> FileSerializer::writeFileIntoCampaign(const QString& destination, const QByteArray& array) 0234 { 0235 qDebug() << "fileserializer" << array.size(); 0236 return QtConcurrent::run([destination, array]() -> bool { return utils::IOHelper::writeFile(destination, array); }); 0237 } 0238 0239 QString FileSerializer::contentTypeToDefaultExtension(Core::ContentType type) 0240 { 0241 using cc= Core::ContentType; 0242 0243 QString res; 0244 switch(type) 0245 { 0246 case cc::VECTORIALMAP: 0247 res= Core::extentions::EXT_MAP; 0248 break; 0249 case cc::PICTURE: 0250 // case cc::ONLINEPICTURE: 0251 res= Core::extentions::EXT_IMG_PNG; 0252 break; 0253 case cc::NOTES: 0254 res= Core::extentions::EXT_TEXT; 0255 break; 0256 case cc::CHARACTERSHEET: 0257 res= Core::extentions::EXT_SHEET; 0258 break; 0259 case cc::SHAREDNOTE: 0260 res= Core::extentions::EXT_SHAREDNOTE; 0261 break; 0262 case cc::PDF: 0263 res= Core::extentions::EXT_PDF; 0264 break; 0265 case cc::WEBVIEW: 0266 res= Core::extentions::EXT_WEBVIEW; 0267 break; 0268 case cc::INSTANTMESSAGING: 0269 res= Core::extentions::EXT_TEXT; 0270 break; 0271 case cc::MINDMAP: 0272 res= Core::extentions::EXT_MINDMAP; 0273 break; 0274 case cc::UNKNOWN: 0275 break; 0276 } 0277 return res; 0278 } 0279 0280 QString FileSerializer::addExtention(const QString& name, Core::ContentType type) 0281 { 0282 auto ext= contentTypeToDefaultExtension(type); 0283 QString ret= name; 0284 if(!name.endsWith(ext)) 0285 ret= QString("%1%2").arg(name, ext); 0286 return ret; 0287 } 0288 0289 bool FileSerializer::isValidCampaignDirectory(const QString& path, bool acceptEmpty) 0290 { 0291 if(path.isEmpty()) 0292 return false; 0293 0294 QDir direct(path); 0295 auto entrylist= direct.entryList(QDir::NoDotAndDotDot); 0296 0297 if(acceptEmpty && entrylist.isEmpty()) // empty directory is valid. 0298 return true; 0299 0300 QList<QString> list{campaign::MEDIA_ROOT, campaign::STATE_ROOT, campaign::TRASH_FOLDER, campaign::CHARACTER_ROOT, 0301 campaign::MODEL_FILE}; 0302 return std::all_of(std::begin(list), std::end(list), [path](const QString& subpath) { 0303 return QFileInfo::exists(QString("%1/%2").arg(path, subpath)); 0304 }); 0305 } 0306 0307 bool FileSerializer::hasContent(const QString& path, Core::CampaignDataCategory category) 0308 { 0309 if(path.isEmpty()) 0310 return false; 0311 0312 QDir direct(path); 0313 auto entrylist= direct.entryList(QDir::NoDotAndDotDot); 0314 0315 if(entrylist.isEmpty()) // empty dir has no content 0316 return false; 0317 0318 QSet<Core::CampaignDataCategory> mediaCategories{ 0319 Core::CampaignDataCategory::Images, Core::CampaignDataCategory::Maps, Core::CampaignDataCategory::MindMaps, 0320 Core::CampaignDataCategory::CharacterSheets, Core::CampaignDataCategory::Notes}; 0321 0322 QList<QString> list; 0323 switch(category) 0324 { 0325 case Core::CampaignDataCategory::AudioPlayer1: 0326 list.append(campaign::FIRST_AUDIO_PLAYER_FILE); 0327 break; 0328 case Core::CampaignDataCategory::AudioPlayer2: 0329 list.append(campaign::SECOND_AUDIO_PLAYER_FILE); 0330 break; 0331 case Core::CampaignDataCategory::AudioPlayer3: 0332 list.append(campaign::THIRD_AUDIO_PLAYER_FILE); 0333 break; 0334 case Core::CampaignDataCategory::Images: 0335 case Core::CampaignDataCategory::Maps: 0336 case Core::CampaignDataCategory::MindMaps: 0337 case Core::CampaignDataCategory::CharacterSheets: 0338 case Core::CampaignDataCategory::Notes: 0339 case Core::CampaignDataCategory::WebLink: 0340 case Core::CampaignDataCategory::PDFDoc: 0341 list.append(campaign::MEDIA_ROOT); 0342 list.append(campaign::MODEL_FILE); 0343 break; 0344 case Core::CampaignDataCategory::DiceAlias: 0345 list.append(campaign::DICE_ALIAS_MODEL); 0346 break; 0347 case Core::CampaignDataCategory::CharacterStates: 0348 list.append(campaign::STATE_MODEL); 0349 break; 0350 case Core::CampaignDataCategory::Themes: 0351 list.append(campaign::THEME_FILE); 0352 break; 0353 case Core::CampaignDataCategory::AntagonistList: 0354 list.append(campaign::CHARACTER_ROOT); 0355 list.append(campaign::CHARACTER_MODEL); 0356 break; 0357 } 0358 0359 auto res= std::all_of(std::begin(list), std::end(list), [path](const QString& subpath) { 0360 return QFileInfo::exists(QString("%1/%2").arg(path, subpath)); 0361 }); 0362 0363 if(res && mediaCategories.contains(category)) 0364 { 0365 direct.cd(campaign::MEDIA_ROOT); 0366 auto subentrylist= direct.entryList(QDir::NoDotAndDotDot); 0367 if(category == Core::CampaignDataCategory::Images) 0368 { 0369 res|= std::any_of(std::begin(subentrylist), std::end(subentrylist), [](const QString& item) { 0370 QSet<QString> imgExt{"png", "jpg", "bmp", "gif", "svg"}; 0371 return std::any_of(std::begin(imgExt), std::end(imgExt), 0372 [item](const QString& ext) { return item.endsWith(ext); }); 0373 }); 0374 } 0375 else if(category == Core::CampaignDataCategory::Maps) 0376 { 0377 res|= std::any_of(std::begin(subentrylist), std::end(subentrylist), [](const QString& item) { 0378 QSet<QString> mapExt{"vmap"}; 0379 return std::any_of(std::begin(mapExt), std::end(mapExt), 0380 [item](const QString& ext) { return item.endsWith(ext); }); 0381 }); 0382 } 0383 else if(category == Core::CampaignDataCategory::MindMaps) 0384 { 0385 res|= std::any_of(std::begin(subentrylist), std::end(subentrylist), [](const QString& item) { 0386 QSet<QString> mindmapExt{"rmap"}; 0387 return std::any_of(std::begin(mindmapExt), std::end(mindmapExt), 0388 [item](const QString& ext) { return item.endsWith(ext); }); 0389 }); 0390 } 0391 else if(category == Core::CampaignDataCategory::CharacterSheets) 0392 { 0393 res|= std::any_of(std::begin(subentrylist), std::end(subentrylist), [](const QString& item) { 0394 QSet<QString> sheetExt{"rcs"}; 0395 return std::any_of(std::begin(sheetExt), std::end(sheetExt), 0396 [item](const QString& ext) { return item.endsWith(ext); }); 0397 }); 0398 } 0399 else if(category == Core::CampaignDataCategory::Notes) 0400 { 0401 res|= std::any_of(std::begin(subentrylist), std::end(subentrylist), [](const QString& item) { 0402 QSet<QString> noteExt{"txt", "md", "html"}; 0403 return std::any_of(std::begin(noteExt), std::end(noteExt), 0404 [item](const QString& ext) { return item.endsWith(ext); }); 0405 }); 0406 } 0407 else if(category == Core::CampaignDataCategory::WebLink) 0408 { 0409 res|= std::any_of(std::begin(subentrylist), std::end(subentrylist), [](const QString& item) { 0410 QSet<QString> noteExt{"txt", "md", "html"}; 0411 return std::any_of(std::begin(noteExt), std::end(noteExt), 0412 [item](const QString& ext) { return item.endsWith(ext); }); 0413 }); 0414 } 0415 else if(category == Core::CampaignDataCategory::PDFDoc) 0416 { 0417 res|= std::any_of(std::begin(subentrylist), std::end(subentrylist), [](const QString& item) { 0418 QSet<QString> noteExt{"txt", "md", "html"}; 0419 return std::any_of(std::begin(noteExt), std::end(noteExt), 0420 [item](const QString& ext) { return item.endsWith(ext); }); 0421 }); 0422 } 0423 } 0424 0425 return res; 0426 } 0427 } // namespace campaign