MythTV master
deletethread.cpp
Go to the documentation of this file.
1#include <cstdlib>
2#include <iostream>
3#include <thread>
4
5#include <fcntl.h>
6
7#include <QList>
8#include <QTimer>
9#include <QString>
10#include <QFileInfo>
11#include <QDateTime>
12#include <QStringList>
13#include <QMutexLocker>
14
16#include "libmythbase/mythdb.h"
19
21
22/*
23 Rather than attempt to calculate a delete speed from tuner card information
24 that may be completely irrelevent to a machine that does not record, just
25 choose a reasonable value.
26
27 38 Mbps (full QAM-256 multiplex) * 4 tuners = 9961472B/0.5s
28*/
29
31 MThread("Delete")
32{
33 m_slow = (bool) gCoreContext->GetNumSetting("TruncateDeletesSlowly", 0);
34 m_link = (bool) gCoreContext->GetNumSetting("DeletesFollowLinks", 0);
35}
36
38{
39 RunProlog();
40
41 LOG(VB_FILE, LOG_DEBUG, "Spawning new delete thread.");
42
43 while (gCoreContext && m_run)
44 {
45 // loop through any stored files every half second
46 ProcessNew();
47 ProcessOld();
48 std::this_thread::sleep_for(500ms);
49 }
50
51 if (!m_files.empty())
52 {
53 // this will only happen if the program is closing, so fast
54 // deletion is not a problem
55 QList<DeleteHandler*>::iterator i;
56 for (i = m_files.begin(); i != m_files.end(); ++i)
57 {
58 (*i)->Close();
59 (*i)->DecrRef();
60 }
61 m_files.clear();
62 }
63 else
64 {
65 LOG(VB_FILE, LOG_DEBUG, "Delete thread self-terminating due to idle.");
66 }
67
68 RunEpilog();
69}
70
71bool DeleteThread::AddFile(const QString& path)
72{
73 // check if a file exists, and add to the list of new files to be deleted
74 QFileInfo finfo(path);
75 if (!finfo.exists())
76 return false;
77
78 QMutexLocker lock(&m_newlock);
79 auto *handler = new DeleteHandler(path);
80 m_newfiles << handler;
81 return true;
82}
83
85{
86 handler->IncrRef();
87 QMutexLocker lock(&m_newlock);
88 m_newfiles << handler;
89 return true;
90}
91
93{
94 // loop through new files, unlinking and adding for deletion
95 // until none are left
96
97 QDateTime ctime = MythDate::current();
98
99 while (true)
100 {
101 // pull a new path from the stack
102 DeleteHandler *handler = nullptr;
103 {
104 QMutexLocker lock(&m_newlock);
105 if (m_newfiles.isEmpty())
106 break;
107 handler = m_newfiles.takeFirst();
108 }
109
110 // empty path given to delete thread, this should not happen
111 //if (path.isEmpty())
112 // continue;
113
114 QString path = handler->m_path;
115 QByteArray cpath_ba = handler->m_path.toLocal8Bit();
116 const char *cpath = cpath_ba.constData();
117
118 QFileInfo finfo(handler->m_path);
119 if (finfo.isSymLink())
120 {
121 if (m_link)
122 {
123 // if file is a symlink and symlinks are processed,
124 // grab the target of the link, and attempt to unlink
125 // the link itself
126 QString tmppath = getSymlinkTarget(handler->m_path);
127
128 if (unlink(cpath))
129 {
130 LOG(VB_GENERAL, LOG_ERR,
131 QString("Error deleting '%1' -> '%2': ")
132 .arg(handler->m_path, tmppath) + ENO);
133 handler->DeleteFailed();
134 handler->DecrRef();
135 continue;
136 }
137
138 // if successful, emit that the link has been removed,
139 // and continue processing the target of the link as
140 // normal
141 //
142 // this may cause problems in which the link is unlinked
143 // signalling the matching metadata for removal, but the
144 // target itself fails, resulting in a spurious file in
145 // an external directory with no link into mythtv
146 handler->DeleteSucceeded();
147 handler->m_path = tmppath;
148 cpath_ba = handler->m_path.toLocal8Bit();
149 cpath = cpath_ba.constData();
150 }
151 else
152 {
153 // symlinks are not followed, so unlink the link
154 // itself and continue
155 if (unlink(cpath))
156 {
157 LOG(VB_GENERAL, LOG_ERR,
158 QString("Error deleting '%1': count not unlink ")
159 .arg(path) + ENO);
160 handler->DeleteFailed();
161 }
162 else
163 {
164 handler->DeleteFailed();
165 }
166
167 handler->DecrRef();
168 continue;
169 }
170 }
171
172 // open the file so it can be unlinked without immediate deletion
173 LOG(VB_FILE, LOG_INFO, QString("About to unlink/delete file: '%1'")
174 .arg(handler->m_path));
175 int fd = open(cpath, O_WRONLY);
176 if (fd == -1)
177 {
178 LOG(VB_FILE, LOG_INFO, QString("About to unlink/delete file"));
179
180 QDir dir(cpath);
181 if(MythRemoveDirectory(dir))
182 {
183 LOG(VB_GENERAL, LOG_ERR,
184 QString("Error deleting '%1': is no directory ")
185 .arg(cpath) + ENO);
186 handler->DeleteFailed();
187 handler->DecrRef();
188 continue;
189 }
190 }
191 // unlink the file so as soon as it is closed, the system will
192 // delete it from the filesystem
193 else if (unlink(cpath))
194 {
195 LOG(VB_GENERAL, LOG_ERR,
196 QString("Error deleting '%1': could not unlink ")
197 .arg(path) + ENO);
198 handler->DeleteFailed();
199 close(fd);
200 handler->DecrRef();
201 continue;
202 }
203
204 handler->DeleteSucceeded();
205
206 // insert the file into a queue of opened references to be deleted
207 handler->m_fd = fd;
208 handler->m_size = finfo.size();
209 handler->m_wait = ctime.addSecs(3); // delay deletion a bit to allow
210 // UI to get any needed IO time
211
212 m_files << handler;
213 }
214}
215
217{
218 // im the only thread accessing this, so no need for a lock
219 if (m_files.empty())
220 return;
221
222 QDateTime ctime = MythDate::current();
223
224 // only operate on one file at a time
225 // delete that file completely before moving onto the next
226 while (true)
227 {
228 DeleteHandler *handler = m_files.first();
229
230 // first file in the list has been delayed for deletion
231 if (handler->m_wait > ctime)
232 break;
233
234 if (m_slow)
235 {
236 handler->m_size -= m_increment;
237 int err = ftruncate(handler->m_fd, handler->m_size);
238
239 if (err)
240 {
241 LOG(VB_GENERAL, LOG_ERR, QString("Error truncating '%1'")
242 .arg(handler->m_path) + ENO);
243 handler->m_size = 0;
244 }
245 }
246 else
247 {
248 handler->m_size = 0;
249 }
250
251 if (handler->m_size == 0)
252 {
253 handler->Close();
254 m_files.removeFirst();
255 handler->DecrRef();
256 }
257
258 // fast delete can close out all, but slow delete needs
259 // to return to sleep
260 if (m_slow || m_files.empty())
261 break;
262 }
263}
void Close(void)
QDateTime m_wait
virtual void DeleteSucceeded(void)
virtual void DeleteFailed(void)
void ProcessNew(void)
DeleteThread(void)
void ProcessOld(void)
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
QList< DeleteHandler * > m_newfiles
Definition: deletethread.h:40
QList< DeleteHandler * > m_files
Definition: deletethread.h:43
bool AddFile(const QString &path)
QMutex m_newlock
Definition: deletethread.h:41
size_t m_increment
Definition: deletethread.h:35
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:180
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:193
int GetNumSetting(const QString &key, int defaultval=0)
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
virtual int IncrRef(void)
Increments reference count.
#define close
Definition: compat.h:28
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
bool MythRemoveDirectory(QDir &aDir)
QString getSymlinkTarget(const QString &start_file, QStringList *intermediaries, unsigned maxLinks)
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15