Warning, file /office/calligra/libs/widgets/KoResourceServer.h was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*  This file is part of the KDE project
0002 
0003     Copyright (c) 1999 Matthias Elter <elter@kde.org>
0004     Copyright (c) 2003 Patrick Julien <freak@codepimps.org>
0005     Copyright (c) 2005 Sven Langkamp <sven.langkamp@gmail.com>
0006     Copyright (c) 2007 Jan Hambrecht <jaham@gmx.net>
0007     Copyright (C) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
0008     Copyright (c) 2013 Sascha Suelzer <s.suelzer@gmail.com>
0009 
0010     This library is free software; you can redistribute it and/or
0011     modify it under the terms of the GNU Lesser General Public
0012     License as published by the Free Software Foundation; either
0013     version 2.1 of the License, or (at your option) any later version.
0014 
0015     This library is distributed in the hope that it will be useful,
0016     but WITHOUT ANY WARRANTY; without even the implied warranty of
0017     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0018     Lesser General Public License for more details.
0019 
0020     You should have received a copy of the GNU Lesser General Public
0021     License along with this library; if not, write to the Free Software
0022     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0023  */
0024 
0025 #ifndef KORESOURCESERVER_H
0026 #define KORESOURCESERVER_H
0027 
0028 #include <QMutex>
0029 #include <QString>
0030 #include <QStringList>
0031 #include <QList>
0032 #include <QFileInfo>
0033 #include <QDir>
0034 
0035 #include <QTemporaryFile>
0036 #include <QDomDocument>
0037 #include "KoResource.h"
0038 #include "KoResourceServerPolicies.h"
0039 #include "KoResourceServerObserver.h"
0040 #include "KoResourceTagStore.h"
0041 #include "KoResourcePaths.h"
0042 
0043 #include "kowidgets_export.h"
0044 #include "WidgetsDebug.h"
0045 
0046 class KoResource;
0047 
0048 /**
0049  * KoResourceServerBase is the base class of all resource servers
0050  */
0051 class KOWIDGETS_EXPORT KoResourceServerBase {
0052 
0053 public:
0054     /**
0055     * Constructs a KoResourceServerBase
0056     * @param type type, has to be the same as used by KoResourcePaths
0057     * @param extensions the file extensions separate by ':', e.g. "*.kgr:*.svg:*.ggr"
0058     */
0059     KoResourceServerBase(const QString& type, const QString& extensions)
0060         : m_type(type)
0061         , m_extensions(extensions)
0062     {
0063     }
0064 
0065     virtual ~KoResourceServerBase() {}
0066 
0067     virtual int resourceCount() const = 0;
0068     virtual void loadResources(QStringList filenames) = 0;
0069     virtual QStringList blackListedFiles() const = 0;
0070     virtual QStringList queryResources(const QString &query) const = 0;
0071     QString type() const { return m_type; }
0072 
0073     /**
0074     * File extensions for resources of the server
0075     * @returns the file extensions separated by ':', e.g. "*.kgr:*.svg:*.ggr"
0076     */
0077     QString extensions() const { return m_extensions; }
0078 
0079     QStringList fileNames() const
0080     {
0081         QStringList extensionList = m_extensions.split(':');
0082         QStringList fileNames;
0083 
0084         foreach (const QString &extension, extensionList) {
0085             fileNames += KoResourcePaths::findAllResources(type().toLatin1(), extension, KoResourcePaths::Recursive | KoResourcePaths::NoDuplicates);
0086 
0087         }
0088         return fileNames;
0089     }
0090 
0091 protected:
0092 
0093     friend class KoResourceTagStore;
0094     virtual KoResource *byMd5(const QByteArray &md5) const = 0;
0095     virtual KoResource *byFileName(const QString &fileName) const = 0;
0096 
0097 private:
0098     QString m_type;
0099     QString m_extensions;
0100 
0101 protected:
0102 
0103     QMutex m_loadLock;
0104 
0105 };
0106 
0107 /**
0108  * KoResourceServer manages the resources of one type. It stores,
0109  * loads and saves the resources.  To keep track of changes the server
0110  * can be observed with a KoResourceServerObserver
0111  *
0112  * The \p Policy template parameter defines the way how the lifetime
0113 
0114  * of a resource is handled.  There are to predefined policies:
0115 
0116  *
0117  *   o PointerStoragePolicy --- usual pointers with ownership over
0118  *                              the resource.
0119 
0120  *   o SharedPointerStoragePolicy --- shared pointers. The server does no
0121  *                                    extra handling for the lifetime of
0122  *                                    the resource.
0123  *
0124  * Use the former for usual resources and the latter for shared pointer based
0125  * ones.
0126  */
0127 
0128 template <class T, class Policy = PointerStoragePolicy<T> >
0129 class KoResourceServer : public KoResourceServerBase
0130 {
0131 public:
0132     typedef typename Policy::PointerType PointerType;
0133     typedef KoResourceServerObserver<T, Policy> ObserverType;
0134     KoResourceServer(const QString& type, const QString& extensions)
0135         : KoResourceServerBase(type, extensions)
0136     {
0137         m_blackListFile = KoResourcePaths::locateLocal("data", "krita/" + type + ".blacklist");
0138         m_blackListFileNames = readBlackListFile();
0139         m_tagStore = new KoResourceTagStore(this);
0140         m_tagStore->loadTags();
0141     }
0142 
0143     ~KoResourceServer() override
0144     {
0145         if (m_tagStore) {
0146             delete m_tagStore;
0147         }
0148 
0149         foreach(ObserverType* observer, m_observers) {
0150             observer->unsetResourceServer();
0151         }
0152 
0153         foreach(PointerType res, m_resources) {
0154             Policy::deleteResource(res);
0155         }
0156 
0157         m_resources.clear();
0158 
0159     }
0160 
0161     int resourceCount() const override {
0162         return m_resources.size();
0163     }
0164 
0165     /**
0166      * Loads a set of resources and adds them to the resource server.
0167      * If a filename appears twice the resource will only be added once. Resources that can't
0168      * be loaded or and invalid aren't added to the server.
0169      * @param filenames list of filenames to be loaded
0170      */
0171     void loadResources(QStringList filenames) override {
0172 
0173         QStringList uniqueFiles;
0174 
0175         while (!filenames.empty()) {
0176 
0177             QString front = filenames.first();
0178             filenames.pop_front();
0179 
0180             // In the save location, people can use sub-folders... And then they probably want
0181             // to load both versions! See https://bugs.kde.org/show_bug.cgi?id=321361.
0182             QString fname;
0183             if (front.contains(saveLocation())) {
0184                 fname = front.split(saveLocation())[1];
0185             }
0186             else {
0187                 fname = QFileInfo(front).fileName();
0188             }
0189 
0190             // XXX: Don't load resources with the same filename. Actually, we should look inside
0191             //      the resource to find out whether they are really the same, but for now this
0192             //      will prevent the same brush etc. showing up twice.
0193             if (!uniqueFiles.contains(fname)) {
0194                 m_loadLock.lock();
0195                 uniqueFiles.append(fname);
0196                 QList<PointerType> resources = createResources(front);
0197                 foreach(PointerType resource, resources) {
0198                     Q_CHECK_PTR(resource);
0199                     if (resource->load() && resource->valid() && !resource->md5().isEmpty()) {
0200                         QByteArray md5 = resource->md5();
0201                         m_resourcesByMd5[md5] = resource;
0202 
0203                         m_resourcesByFilename[resource->shortFilename()] = resource;
0204 
0205                         if (resource->name().isEmpty()) {
0206                             resource->setName(fname);
0207                         }
0208                         if (m_resourcesByName.contains(resource->name())) {
0209                             resource->setName(resource->name() + "(" + resource->shortFilename() + ")");
0210                         }
0211                         m_resourcesByName[resource->name()] = resource;
0212                         notifyResourceAdded(resource);
0213                     }
0214                     else {
0215                         warnWidgets << "Loading resource " << front << "failed";
0216                         Policy::deleteResource(resource);
0217                     }
0218                 }
0219                 m_loadLock.unlock();
0220             }
0221         }
0222 
0223         m_resources = sortedResources();
0224 
0225         foreach(ObserverType* observer, m_observers) {
0226             observer->syncTaggedResourceView();
0227         }
0228 
0229         debugWidgets << "done loading  resources for type " << type();
0230     }
0231 
0232 
0233     /// Adds an already loaded resource to the server
0234     bool addResource(PointerType resource, bool save = true, bool infront = false) {
0235         if (!resource->valid()) {
0236             warnWidgets << "Tried to add an invalid resource!";
0237             return false;
0238         }
0239 
0240         if (save) {
0241             QFileInfo fileInfo(resource->filename());
0242 
0243             QDir d(fileInfo.path());
0244             if (!d.exists()) {
0245                 d.mkdir(fileInfo.path());
0246             }
0247 
0248             if (fileInfo.exists()) {
0249                 QString filename = fileInfo.path() + "/" + fileInfo.baseName() + "XXXXXX" + "." + fileInfo.suffix();
0250                 debugWidgets << "fileName is " << filename;
0251                 QTemporaryFile file(filename);
0252                 if (file.open()) {
0253                     debugWidgets << "now " << file.fileName();
0254                     resource->setFilename(file.fileName());
0255                 }
0256             }
0257 
0258             if (!resource->save()) {
0259                 warnWidgets << "Could not save resource!";
0260                 return false;
0261             }
0262         }
0263 
0264         Q_ASSERT(!resource->filename().isEmpty() || !resource->name().isEmpty());
0265         if (resource->filename().isEmpty()) {
0266             resource->setFilename(resource->name());
0267         }
0268         else if (resource->name().isEmpty()) {
0269             resource->setName(resource->filename());
0270         }
0271 
0272         m_resourcesByFilename[resource->shortFilename()] = resource;
0273         m_resourcesByMd5[resource->md5()] = resource;
0274         m_resourcesByName[resource->name()] = resource;
0275         if (infront) {
0276             m_resources.insert(0, resource);
0277         }
0278         else {
0279             m_resources.append(resource);
0280         }
0281 
0282         notifyResourceAdded(resource);
0283 
0284         return true;
0285     }
0286     
0287     /*Removes a given resource from the blacklist.
0288      */
0289     bool removeFromBlacklist(PointerType resource) {
0290         if (m_blackListFileNames.contains(resource->filename())) {
0291             m_blackListFileNames.removeAll(resource->filename());
0292             writeBlackListFile();
0293         } else{
0294             warnWidgets<<"Doesn't contain filename";
0295             return false;
0296         }
0297         
0298         
0299         //then return true//
0300         return true;
0301     }
0302     /// Remove a resource from Resource Server but not from a file
0303     bool removeResourceFromServer(PointerType resource){
0304         if ( !m_resourcesByFilename.contains( resource->shortFilename() ) ) {
0305             return false;
0306         }
0307         m_resourcesByMd5.remove(resource->md5());
0308         m_resourcesByName.remove(resource->name());
0309         m_resourcesByFilename.remove(resource->shortFilename());
0310         m_resources.removeAt(m_resources.indexOf(resource));
0311         m_tagStore->removeResource(resource);
0312         notifyRemovingResource(resource);
0313 
0314         Policy::deleteResource(resource);
0315         return true;
0316     }
0317 
0318     /// Remove a resource from the resourceserver and blacklist it
0319 
0320     bool removeResourceAndBlacklist(PointerType resource) {
0321 
0322         if ( !m_resourcesByFilename.contains( resource->shortFilename() ) ) {
0323             return false;
0324         }
0325         m_resourcesByMd5.remove(resource->md5());
0326         m_resourcesByName.remove(resource->name());
0327         m_resourcesByFilename.remove(resource->shortFilename());
0328         m_resources.removeAt(m_resources.indexOf(resource));
0329         m_tagStore->removeResource(resource);
0330         notifyRemovingResource(resource);
0331 
0332         m_blackListFileNames.append(resource->filename());
0333         writeBlackListFile();
0334         Policy::deleteResource(resource);
0335         return true;
0336     }
0337 
0338     QList<PointerType> resources() {
0339         m_loadLock.lock();
0340         QList<PointerType> resourceList = m_resources;
0341         foreach(PointerType r, m_resourceBlackList) {
0342             resourceList.removeOne(r);
0343         }
0344         m_loadLock.unlock();
0345         return resourceList;
0346     }
0347 
0348     /// Returns path where to save user defined and imported resources to
0349     virtual QString saveLocation() {
0350         return KoResourcePaths::saveLocation(type().toLatin1());
0351     }
0352 
0353     /**
0354      * Creates a new resource from a given file and adds them to the resource server
0355      * The base implementation does only load one resource per file, override to implement collections
0356      * @param filename file name of the resource file to be imported
0357      * @param fileCreation decides whether to create the file in the saveLocation() directory
0358      */
0359     virtual bool importResourceFile(const QString & filename , bool fileCreation=true) {
0360 
0361         QFileInfo fi(filename);
0362         if (!fi.exists())
0363             return false;
0364         if ( fi.size() == 0)
0365             return false;
0366 
0367         PointerType resource = createResource( filename );
0368         resource->load();
0369         if (!resource->valid()) {
0370             warnWidgets << "Import failed! Resource is not valid";
0371             Policy::deleteResource(resource);
0372 
0373             return false;
0374 
0375         }
0376 
0377         if (fileCreation) {
0378             Q_ASSERT(!resource->defaultFileExtension().isEmpty());
0379             Q_ASSERT(!saveLocation().isEmpty());
0380 
0381             QString newFilename = saveLocation() + fi.baseName() + resource->defaultFileExtension();
0382             QFileInfo fileInfo(newFilename);
0383 
0384             int i = 1;
0385             while (fileInfo.exists()) {
0386                 fileInfo.setFile(saveLocation() + fi.baseName() + QString("%1").arg(i) + resource->defaultFileExtension());
0387                 i++;
0388             }
0389             resource->setFilename(fileInfo.filePath());
0390         }
0391 
0392 
0393         if(!addResource(resource)) {
0394             Policy::deleteResource(resource);
0395         }
0396 
0397         return true;
0398     }
0399 
0400     /// Removes the resource file from the resource server
0401     virtual void removeResourceFile(const QString & filename)
0402     {
0403         QFileInfo fi(filename);
0404 
0405         PointerType resource = resourceByFilename(fi.fileName());
0406         if (!resource) {
0407             warnWidgets << "Resource file do not exist ";
0408             return;
0409         }
0410 
0411         if (!removeResourceFromServer(resource))
0412             return;
0413     }
0414 
0415 
0416     /**
0417      * Addes an observer to the server
0418      * @param observer the observer to be added
0419      * @param notifyLoadedResources determines if the observer should be notified about the already loaded resources
0420      */
0421     void addObserver(ObserverType* observer, bool notifyLoadedResources = true)
0422     {
0423         m_loadLock.lock();
0424         if(observer && !m_observers.contains(observer)) {
0425             m_observers.append(observer);
0426 
0427             if(notifyLoadedResources) {
0428                 foreach(PointerType resource, m_resourcesByFilename) {
0429                     observer->resourceAdded(resource);
0430 
0431                 }
0432             }
0433         }
0434         m_loadLock.unlock();
0435     }
0436 
0437     /**
0438      * Removes an observer from the server
0439      * @param observer the observer to be removed
0440      */
0441     void removeObserver(ObserverType* observer)
0442     {
0443         int index = m_observers.indexOf( observer );
0444         if( index < 0 )
0445             return;
0446 
0447         m_observers.removeAt( index );
0448     }
0449 
0450     PointerType resourceByFilename(const QString& filename) const
0451     {
0452         if (m_resourcesByFilename.contains(filename)) {
0453             return m_resourcesByFilename[filename];
0454         }
0455         return 0;
0456     }
0457 
0458 
0459     PointerType resourceByName( const QString& name ) const
0460     {
0461         if (m_resourcesByName.contains(name)) {
0462             return m_resourcesByName[name];
0463         }
0464         return 0;
0465     }
0466 
0467     PointerType resourceByMD5(const QByteArray& md5) const
0468     {
0469         return m_resourcesByMd5.value(md5);
0470     }
0471 
0472     /**
0473      * Call after changing the content of a resource;
0474      * Notifies the connected views.
0475      */
0476     void updateResource( PointerType resource )
0477     {
0478         notifyResourceChanged(resource);
0479     }
0480 
0481     QStringList blackListedFiles() const override
0482     {
0483         return m_blackListFileNames;
0484     }
0485 
0486     void removeBlackListedFiles() {
0487         QStringList remainingFiles; // Files that can't be removed e.g. no rights will stay blacklisted
0488         foreach(const QString &filename, m_blackListFileNames) {
0489             QFile file( filename );
0490             if( ! file.remove() ) {
0491                 remainingFiles.append(filename);
0492             }
0493         }
0494         m_blackListFileNames = remainingFiles;
0495         writeBlackListFile();
0496     }
0497 
0498     QStringList tagNamesList() const
0499     {
0500         return m_tagStore->tagNamesList();
0501     }
0502 
0503     // don't use these method directly since it doesn't update views!
0504     void addTag( KoResource* resource,const QString& tag)
0505     {
0506         m_tagStore->addTag(resource,tag);
0507     }
0508 
0509     // don't use these method directly since it doesn't update views!
0510     void delTag( KoResource* resource,const QString& tag)
0511     {
0512         m_tagStore->delTag(resource,tag);
0513     }
0514 
0515     QStringList searchTag(const QString& lineEditText)
0516     {
0517         return m_tagStore->searchTag(lineEditText);
0518     }
0519 
0520     void tagCategoryAdded(const QString& tag)
0521     {
0522         m_tagStore->serializeTags();
0523         foreach(ObserverType* observer, m_observers) {
0524             observer->syncTagAddition(tag);
0525         }
0526     }
0527 
0528     void tagCategoryRemoved(const QString& tag)
0529     {
0530         m_tagStore->delTag(tag);
0531         m_tagStore->serializeTags();
0532         foreach(ObserverType* observer, m_observers) {
0533             observer->syncTagRemoval(tag);
0534         }
0535     }
0536 
0537     void tagCategoryMembersChanged()
0538     {
0539         m_tagStore->serializeTags();
0540         foreach(ObserverType* observer, m_observers) {
0541             observer->syncTaggedResourceView();
0542         }
0543     }
0544 
0545     QStringList queryResources(const QString &query) const override
0546     {
0547         return m_tagStore->searchTag(query);
0548     }
0549 
0550     QStringList assignedTagsList(KoResource* resource) const
0551     {
0552         return m_tagStore->assignedTagsList(resource);
0553     }
0554 
0555 
0556     /**
0557      * Create one or more resources from a single file. By default one resource is created.
0558      * Override to create more resources from the file.
0559      * @param filename the filename of the resource or resource collection
0560      */
0561     virtual QList<PointerType> createResources( const QString & filename )
0562     {
0563         QList<PointerType> createdResources;
0564         createdResources.append(createResource(filename));
0565         return createdResources;
0566     }
0567 
0568     virtual PointerType createResource( const QString & filename ) = 0;
0569 
0570     /// Return the currently stored resources in alphabetical order, overwrite for customized sorting
0571     virtual QList<PointerType> sortedResources()
0572     {
0573         QMap<QString, PointerType> sortedNames;
0574         foreach(const QString &name, m_resourcesByName.keys()) {
0575             sortedNames.insert(name.toLower(), m_resourcesByName[name]);
0576         }
0577         return sortedNames.values();
0578     }
0579 
0580 protected:
0581 
0582     void notifyResourceAdded(PointerType resource)
0583     {
0584         foreach(ObserverType* observer, m_observers) {
0585             observer->resourceAdded(resource);
0586         }
0587     }
0588 
0589     void notifyRemovingResource(PointerType resource)
0590     {
0591         foreach(ObserverType* observer, m_observers) {
0592             observer->removingResource(resource);
0593         }
0594     }
0595 
0596     void notifyResourceChanged(PointerType resource)
0597     {
0598         foreach(ObserverType* observer, m_observers) {
0599             observer->resourceChanged(resource);
0600         }
0601     }
0602 
0603     /// Reads the xml file and returns the filenames as a list
0604     QStringList readBlackListFile()
0605     {
0606         QStringList filenameList;
0607 
0608         QFile f(m_blackListFile);
0609         if (!f.open(QIODevice::ReadOnly)) {
0610             return filenameList;
0611         }
0612 
0613         QDomDocument doc;
0614         if (!doc.setContent(&f)) {
0615             warnWidgets << "The file could not be parsed.";
0616             return filenameList;
0617         }
0618 
0619         QDomElement root = doc.documentElement();
0620         if (root.tagName() != "resourceFilesList") {
0621             warnWidgets << "The file doesn't seem to be of interest.";
0622             return filenameList;
0623         }
0624 
0625         QDomElement file = root.firstChildElement("file");
0626 
0627         while (!file.isNull()) {
0628             QDomNode n = file.firstChild();
0629             QDomElement e = n.toElement();
0630             if (e.tagName() == "name") {
0631                 filenameList.append((e.text()).replace(QString("~"),QDir::homePath()));
0632             }
0633             file = file.nextSiblingElement("file");
0634         }
0635         return filenameList;
0636     }
0637 
0638     /// write the blacklist file entries to an xml file
0639     void writeBlackListFile()
0640     {
0641         QDir().mkpath(QFileInfo(m_blackListFile).path());
0642         QFile f(m_blackListFile);
0643         if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
0644             warnWidgets << "Cannot write meta information to '" << m_blackListFile << "'." << endl;
0645             return;
0646         }
0647 
0648         QDomDocument doc;
0649         QDomElement root;
0650 
0651         QDomDocument docTemp("m_blackListFile");
0652         doc = docTemp;
0653         doc.appendChild(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""));
0654         root = doc.createElement("resourceFilesList");
0655         doc.appendChild(root);
0656 
0657         foreach(QString filename, m_blackListFileNames) {
0658             QDomElement fileEl = doc.createElement("file");
0659             QDomElement nameEl = doc.createElement("name");
0660             QDomText nameText = doc.createTextNode(filename.replace(QDir::homePath(),QString("~")));
0661             nameEl.appendChild(nameText);
0662             fileEl.appendChild(nameEl);
0663             root.appendChild(fileEl);
0664         }
0665 
0666         QTextStream metastream(&f);
0667         metastream << doc.toString();
0668         f.close();
0669     }
0670 
0671 protected:
0672 
0673     KoResource* byMd5(const QByteArray &md5) const override
0674     {
0675         return Policy::toResourcePointer(resourceByMD5(md5));
0676     }
0677 
0678     KoResource* byFileName(const QString &fileName) const override
0679     {
0680         return Policy::toResourcePointer(resourceByFilename(fileName));
0681     }
0682 
0683 private:
0684 
0685     QHash<QString, PointerType> m_resourcesByName;
0686     QHash<QString, PointerType> m_resourcesByFilename;
0687     QHash<QByteArray, PointerType> m_resourcesByMd5;
0688 
0689     QList<PointerType> m_resourceBlackList;
0690     QList<PointerType> m_resources; ///< list of resources in order of addition
0691     QList<ObserverType*> m_observers;
0692     QString m_blackListFile;
0693     QStringList m_blackListFileNames;
0694     KoResourceTagStore* m_tagStore;
0695 
0696 };
0697 
0698 template <class T, class Policy = PointerStoragePolicy<T> >
0699     class KoResourceServerSimpleConstruction : public KoResourceServer<T, Policy>
0700 {
0701 public:
0702     KoResourceServerSimpleConstruction(const QString& type, const QString& extensions)
0703 : KoResourceServer<T, Policy>(type, extensions)
0704     {
0705     }
0706 
0707 typename KoResourceServer<T, Policy>::PointerType createResource( const QString & filename ) override {
0708         return new T(filename);
0709     }
0710 };
0711 
0712 #endif // KORESOURCESERVER_H