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