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