1 /************************************************************************
2 **
3 **  Copyright (C) 2015-2021 Kevin B. Hendricks, Stratford, Ontario Canada
4 **  Copyright (C) 2009-2011 Strahinja Markovic  <strahinja.markovic@gmail.com>
5 **
6 **  This file is part of Sigil.
7 **
8 **  Sigil is free software: you can redistribute it and/or modify
9 **  it under the terms of the GNU General Public License as published by
10 **  the Free Software Foundation, either version 3 of the License, or
11 **  (at your option) any later version.
12 **
13 **  Sigil is distributed in the hope that it will be useful,
14 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 **  GNU General Public License for more details.
17 **
18 **  You should have received a copy of the GNU General Public License
19 **  along with Sigil.  If not, see <http://www.gnu.org/licenses/>.
20 **
21 *************************************************************************/
22 
23 #include <QtCore/QDir>
24 #include <QtCore/QDateTime>
25 #include <QtCore/QFileInfo>
26 #include <QtCore/QString>
27 #include <QtCore/QTimer>
28 #include <QtWidgets/QFileIconProvider>
29 
30 #include "Misc/Utility.h"
31 #include "ResourceObjects/Resource.h"
32 
33 const int WAIT_FOR_WRITE_DELAY = 100;
34 
Resource(const QString & mainfolder,const QString & fullfilepath,QObject * parent)35 Resource::Resource(const QString &mainfolder, const QString &fullfilepath, QObject *parent)
36     :
37     QObject(parent),
38     m_Identifier(Utility::CreateUUID()),
39     m_MainFolder(mainfolder),
40     m_FullFilePath(fullfilepath),
41     m_LastSaved(0),
42     m_LastWrittenTo(0),
43     m_LastWrittenSize(0),
44     m_CurrentBookRelPath(""),
45     m_EpubVersion("2.0"),
46     m_MediaType(""),
47     m_ReadWriteLock(QReadWriteLock::Recursive)
48 {
49 }
50 
operator <(const Resource & other)51 bool Resource::operator< (const Resource &other)
52 {
53     return Filename() < other.Filename();
54 }
55 
56 
GetIdentifier() const57 QString Resource::GetIdentifier() const
58 {
59     return m_Identifier;
60 }
61 
62 
Filename() const63 QString Resource::Filename() const
64 {
65     return GetRelativePath().split('/').last();
66 }
67 
68 
69 
70 // relative path of the resource's directory within the EPUB (a book path to the folder)
GetFolder() const71 QString Resource::GetFolder() const
72 {
73     return QFileInfo(GetRelativePath()).path();
74 }
75 
76 
77 // Pathname of the file within the EPUB.  Sometimes called the book path
GetRelativePath() const78 QString Resource::GetRelativePath() const
79 {
80     // Note m_MainFolder *never* ends with a path separator - see Misc/TempFolder.cpp
81     return m_FullFilePath.right(m_FullFilePath.length() - m_MainFolder.length() - 1);
82 }
83 
84 
85 // Generate a unique path segment ending in file name for this resource
ShortPathName() const86 QString Resource::ShortPathName() const
87 {
88     return m_ShortName;
89 }
90 
91 
92 // Use to generate relative path **from** some **other** start_resource
93 // (OPFResource, NCXResource, NavResource, etc) "to" **this** resource
GetRelativePathFromResource(const Resource * start_resource) const94 QString Resource::GetRelativePathFromResource(const Resource* start_resource) const
95 {
96     if (GetRelativePath() == start_resource->GetRelativePath()) return "";
97     // return Utility::relativePath(GetRelativePath(), start_resource->GetFolder());
98     return Utility::relativePath(m_FullFilePath, start_resource->GetFullFolderPath());
99 }
100 
101 
102 // Use to generate relative path from **this** resource to  some **other** dest_resource
GetRelativePathToResource(const Resource * dest_resource) const103 QString Resource::GetRelativePathToResource(const Resource* dest_resource) const
104 {
105     if (GetRelativePath() == dest_resource->GetRelativePath()) return "";
106     // return Utility::relativePath(dest_resource->GetRelativePath(), dest_resource->GetFolder());
107     return Utility::relativePath(dest_resource->GetFullPath(), GetFullFolderPath());
108 }
109 
110 
GetFullPath() const111 QString Resource::GetFullPath() const
112 {
113     return m_FullFilePath;
114 }
115 
116 
GetFullFolderPath() const117 QString Resource::GetFullFolderPath() const
118 {
119     return QFileInfo(m_FullFilePath).absolutePath();
120 }
121 
122 
GetBaseUrl() const123 QUrl Resource::GetBaseUrl() const
124 {
125     return QUrl::fromLocalFile(QFileInfo(m_FullFilePath).absolutePath() + "/");
126 }
127 
128 
SetCurrentBookRelPath(const QString & current_path)129 void Resource::SetCurrentBookRelPath(const QString& current_path)
130 {
131     m_CurrentBookRelPath = current_path;
132 }
133 
134 
GetCurrentBookRelPath()135 QString Resource::GetCurrentBookRelPath()
136 {
137     if (m_CurrentBookRelPath.isEmpty()) {
138         return GetRelativePath();
139     }
140     return m_CurrentBookRelPath;
141 }
142 
SetEpubVersion(const QString & version)143 void Resource::SetEpubVersion(const QString& version)
144 {
145     m_EpubVersion = version;
146 }
147 
148 
GetEpubVersion() const149 QString Resource::GetEpubVersion() const
150 {
151   return m_EpubVersion;
152 }
153 
154 
SetMediaType(const QString & mtype)155 void Resource::SetMediaType(const QString& mtype)
156 {
157     m_MediaType = mtype;
158 }
159 
160 
GetMediaType() const161 QString Resource::GetMediaType() const
162 {
163   return m_MediaType;
164 }
165 
166 
SetShortPathName(const QString & shortname)167 void Resource::SetShortPathName(const QString& shortname)
168 {
169     m_ShortName = shortname;
170 }
171 
172 
GetFullPathToBookFolder() const173 QString Resource::GetFullPathToBookFolder() const
174 {
175    return m_MainFolder;
176 }
177 
178 
GetLock() const179 QReadWriteLock &Resource::GetLock() const
180 {
181     return m_ReadWriteLock;
182 }
183 
184 
Icon() const185 QIcon Resource::Icon() const
186 {
187     return QFileIconProvider().icon(QFileInfo(m_FullFilePath));
188 }
189 
190 
RenameTo(const QString & new_filename)191 bool Resource::RenameTo(const QString &new_filename)
192 {
193     QString new_path;
194     bool successful = false;
195     {
196         QWriteLocker locker(&m_ReadWriteLock);
197         new_path = QFileInfo(m_FullFilePath).absolutePath() + "/" + new_filename;
198         successful = Utility::RenameFile(m_FullFilePath, new_path);
199     }
200 
201     if (successful) {
202         QString old_path = m_FullFilePath;
203         m_FullFilePath = new_path;
204         SetShortPathName(new_filename);
205         emit Renamed(this, old_path);
206     }
207 
208     return successful;
209 }
210 
MoveTo(const QString & new_bookpath)211 bool Resource::MoveTo(const QString &new_bookpath)
212 {
213     QString new_path;
214     bool successful = false;
215     {
216         QWriteLocker locker(&m_ReadWriteLock);
217         new_path = GetFullPathToBookFolder() + "/" + new_bookpath;
218         successful = Utility::SMoveFile(m_FullFilePath, new_path);
219     }
220 
221     if (successful) {
222         QString old_path = m_FullFilePath;
223         m_FullFilePath = new_path;
224         emit Moved(this, old_path);
225     }
226 
227     return successful;
228 }
229 
Delete()230 bool Resource::Delete()
231 {
232     bool successful = false;
233     {
234         QWriteLocker locker(&m_ReadWriteLock);
235         successful = Utility::SDeleteFile(m_FullFilePath);
236     }
237 
238     if (successful) {
239         emit Deleted(this);
240         // try to prevent any resource modified signals from going out
241         // while we wait for delete to actually happen
242         disconnect(this, 0, 0, 0);
243         deleteLater();
244     }
245 
246     return successful;
247 }
248 
249 
Type() const250 Resource::ResourceType Resource::Type() const
251 {
252     return Resource::GenericResourceType;
253 }
254 
LoadFromDisk()255 bool Resource::LoadFromDisk()
256 {
257     return false;
258 }
259 
SaveToDisk(bool book_wide_save)260 void Resource::SaveToDisk(bool book_wide_save)
261 {
262     const QDateTime lastModifiedDate = QFileInfo(m_FullFilePath).lastModified();
263 
264     if (lastModifiedDate.isValid()) {
265         m_LastSaved = lastModifiedDate.toMSecsSinceEpoch();
266     }
267 }
268 
FileChangedOnDisk()269 void Resource::FileChangedOnDisk()
270 {
271     QFileInfo latestFileInfo(m_FullFilePath);
272     const QDateTime lastModifiedDate = latestFileInfo.lastModified();
273     m_LastWrittenTo = lastModifiedDate.isValid() ? lastModifiedDate.toMSecsSinceEpoch() : 0;
274     m_LastWrittenSize = latestFileInfo.size();
275     QTimer::singleShot(WAIT_FOR_WRITE_DELAY, this, SLOT(ResourceFileModified()));
276 }
277 
ResourceFileModified()278 void Resource::ResourceFileModified()
279 {
280     QFileInfo newFileInfo(m_FullFilePath);
281     const QDateTime lastModifiedDate = newFileInfo.lastModified();
282     qint64 latestWrittenTo = lastModifiedDate.isValid() ? lastModifiedDate.toMSecsSinceEpoch() : 0;
283     qint64 latestWrittenSize = newFileInfo.size();
284 
285     if (latestWrittenTo == m_LastSaved) {
286         // The FileChangedOnDisk has triggered even though the data in the file has not changed.
287         // This can happen if the FileWatcher is monitoring a file that Sigil has just performed
288         // a disk operation with, such as Saving before a Merge. In this circumstance the data
289         // loaded in memory by Sigil may be more up to date than that on disk (such as after the
290         // merge but before user has chosen to Save) so we want to ignore the file change notification.
291         return;
292     }
293 
294     if ((latestWrittenTo != m_LastWrittenTo) || (latestWrittenSize != m_LastWrittenSize)) {
295         // The file is still being written to.
296         m_LastWrittenTo = latestWrittenTo;
297         m_LastWrittenSize = latestWrittenSize;
298         QTimer::singleShot(WAIT_FOR_WRITE_DELAY, this, SLOT(ResourceFileModified()));
299     } else {
300         if (LoadFromDisk()) {
301             // will trigger marking the book as modified
302             emit ResourceUpdatedFromDisk(this);
303         }
304 
305         // will trigger updates in other resources that link to this resource
306         emit ResourceUpdatedOnDisk();
307     }
308 }
309