MythTV master
remotefile.cpp
Go to the documentation of this file.
1#include <iostream>
2
3#include <QtGlobal>
4#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
5#include <QtSystemDetection>
6#endif
7#include <QFile>
8#include <QFileInfo>
9#include <QRegularExpression>
10#include <QUrl>
11
12// POSIX C headers
13#include <unistd.h>
14#include <fcntl.h>
15
16#ifndef O_LARGEFILE
17static constexpr int8_t O_LARGEFILE { 0 };
18#endif
19
20#include "mythdb.h"
21#include "remotefile.h"
22#include "mythcorecontext.h"
23#include "mythsocket.h"
24#include "compat.h"
25#include "mythtimer.h"
26#include "mythdate.h"
27#include "mythmiscutil.h"
28#include "mythlogging.h"
29#include "threadedfilewriter.h"
30#include "storagegroup.h"
31
32static constexpr std::chrono::milliseconds MAX_FILE_CHECK { 500ms };
33
34static bool RemoteSendReceiveStringList(const QString &host, QStringList &strlist)
35{
36 bool ok = false;
37
39 {
40 // since the master backend cannot connect back around to
41 // itself, and the libraries do not have access to the list
42 // of connected slave backends to query an existing connection
43 // start up a new temporary connection directly to the slave
44 // backend to query the file list
45 QString ann = QString("ANN Playback %1 0")
47 QString addr = gCoreContext->GetBackendServerIP(host);
48 int port = gCoreContext->GetBackendServerPort(host);
49 bool mismatch = false;
50
52 addr, port, ann, &mismatch);
53 if (sock)
54 {
55 ok = sock->SendReceiveStringList(strlist);
56 sock->DecrRef();
57 }
58 else
59 {
60 strlist.clear();
61 }
62 }
63 else
64 {
66 }
67
68 return ok;
69}
70
71RemoteFile::RemoteFile(QString url, bool write, bool usereadahead,
72 std::chrono::milliseconds timeout,
73 const QStringList *possibleAuxiliaryFiles) :
74 m_path(std::move(url)),
75 m_useReadAhead(usereadahead), m_timeoutMs(timeout),
76 m_writeMode(write)
77{
78 if (m_writeMode)
79 {
80 m_useReadAhead = false;
81 m_timeoutMs = -1ms;
82 }
83 else if (possibleAuxiliaryFiles)
84 {
85 m_possibleAuxFiles = *possibleAuxiliaryFiles;
86 }
87
88 if (!m_path.isEmpty())
89 Open();
90
91 LOG(VB_FILE, LOG_DEBUG, QString("RemoteFile(%1)").arg(m_path));
92}
93
95{
96 Close();
97 if (m_controlSock)
98 {
100 m_controlSock = nullptr;
101 }
102 if (m_sock)
103 {
104 m_sock->DecrRef();
105 m_sock = nullptr;
106 }
107}
108
109bool RemoteFile::isLocal(const QString &lpath)
110{
111 bool is_local = !lpath.isEmpty() &&
112 !lpath.startsWith("myth:") &&
113 (lpath.startsWith("/") || QFile::exists(lpath));
114 return is_local;
115}
116
117bool RemoteFile::isLocal(void) const
118{
119 return isLocal(m_path);
120}
121
123{
124 QUrl qurl(m_path);
125 QString dir;
126
127 QString host = qurl.host();
128 int port = qurl.port();
129
130 dir = qurl.path();
131
132 if (qurl.hasQuery())
133 dir += "?" + QUrl::fromPercentEncoding(
134 qurl.query(QUrl::FullyEncoded).toLocal8Bit());
135
136 if (qurl.hasFragment())
137 dir += "#" + qurl.fragment();
138
139 QString sgroup = qurl.userName();
140
141 auto *lsock = new MythSocket();
142 QString stype = control ? "control socket" : "file data socket";
143
144 QString loc = QString("RemoteFile::openSocket(%1): ").arg(stype);
145
146 if (port <= 0)
147 {
149 }
150
151 if (!lsock->ConnectToHost(host, port))
152 {
153 LOG(VB_GENERAL, LOG_ERR, loc +
154 QString("Could not connect to server %1:%2") .arg(host).arg(port));
155 lsock->DecrRef();
156 return nullptr;
157 }
158
159 QString hostname = GetMythDB()->GetHostName();
160
161 QStringList strlist;
162
163#ifndef IGNORE_PROTO_VER_MISMATCH
164 if (!gCoreContext->CheckProtoVersion(lsock, 5s))
165 {
166 LOG(VB_GENERAL, LOG_ERR, loc +
167 QString("Failed validation to server %1:%2").arg(host).arg(port));
168 lsock->DecrRef();
169 return nullptr;
170 }
171#endif
172
173 if (control)
174 {
175 strlist.append(QString("ANN Playback %1 %2")
176 .arg(hostname).arg(static_cast<int>(false)));
177 if (!lsock->SendReceiveStringList(strlist))
178 {
179 LOG(VB_GENERAL, LOG_ERR, loc +
180 QString("Could not read string list from server %1:%2")
181 .arg(host).arg(port));
182 lsock->DecrRef();
183 return nullptr;
184 }
185 }
186 else
187 {
188 strlist.reserve(3 + m_possibleAuxFiles.size());
189 strlist.push_back(QString("ANN FileTransfer %1 %2 %3 %4")
190 .arg(hostname).arg(static_cast<int>(m_writeMode))
191 .arg(static_cast<int>(m_useReadAhead)).arg(m_timeoutMs.count()));
192 strlist << QString("%1").arg(dir);
193 strlist << sgroup;
194
195 for (const auto& fname : std::as_const(m_possibleAuxFiles))
196 strlist << fname;
197
198 if (!lsock->SendReceiveStringList(strlist))
199 {
200 LOG(VB_GENERAL, LOG_ERR, loc +
201 QString("Did not get proper response from %1:%2")
202 .arg(host).arg(port));
203 strlist.clear();
204 strlist.push_back("ERROR");
205 strlist.push_back("invalid response");
206 }
207
208 if (strlist.size() >= 3)
209 {
210 auto it = strlist.begin(); ++it;
211 m_recorderNum = (*it).toInt(); ++it;
212 m_fileSize = (*it).toLongLong(); ++it;
213 for (; it != strlist.end(); ++it)
214 m_auxFiles << *it;
215 }
216 else if (!strlist.isEmpty() && strlist.size() < 3 &&
217 strlist[0] != "ERROR")
218 {
219 LOG(VB_GENERAL, LOG_ERR, loc +
220 QString("Did not get proper response from %1:%2")
221 .arg(host).arg(port));
222 strlist.clear();
223 strlist.push_back("ERROR");
224 strlist.push_back("invalid response");
225 }
226 }
227
228 if (strlist.isEmpty() || strlist[0] == "ERROR")
229 {
230 lsock->DecrRef();
231 lsock = nullptr;
232 if (strlist.isEmpty())
233 {
234 LOG(VB_GENERAL, LOG_ERR, loc + "Failed to open socket, timeout");
235 }
236 else
237 {
238 LOG(VB_GENERAL, LOG_ERR, loc + "Failed to open socket" +
239 ((strlist.size() >= 2) ?
240 QString(", error was %1").arg(strlist[1]) :
241 QString(", remote error")));
242 }
243 }
244
245 return lsock;
246}
247
249{
250 if (isLocal())
251 {
252 return m_writeMode ? (m_fileWriter != nullptr) : (m_localFile != -1);
253 }
254 return m_sock && m_controlSock;
255}
256
258{
259 if (isOpen())
260 return true;
261
262 QMutexLocker locker(&m_lock);
263 return OpenInternal();
264}
265
271{
272 if (isLocal())
273 {
274 if (m_writeMode)
275 {
276 // make sure the directories are created if necessary
277 QFileInfo fi(m_path);
278 QDir dir(fi.path());
279 if (!dir.exists())
280 {
281 LOG(VB_FILE, LOG_WARNING, QString("RemoteFile::Open(%1) creating directories")
282 .arg(m_path));
283
284 if (!dir.mkpath(fi.path()))
285 {
286 LOG(VB_GENERAL, LOG_ERR, QString("RemoteFile::Open(%1) failed to create the directories")
287 .arg(m_path));
288 return false;
289 }
290 }
291
293 O_WRONLY|O_TRUNC|O_CREAT|O_LARGEFILE,
294 0644);
295
296 if (!m_fileWriter->Open())
297 {
298 delete m_fileWriter;
299 m_fileWriter = nullptr;
300 LOG(VB_FILE, LOG_ERR, QString("RemoteFile::Open(%1) write mode error")
301 .arg(m_path));
302 return false;
303 }
304 SetBlocking();
305 return true;
306 }
307
308 // local mode, read only
309 if (!Exists(m_path))
310 {
311 LOG(VB_FILE, LOG_ERR,
312 QString("RemoteFile::Open(%1) Error: Does not exist").arg(m_path));
313 return false;
314 }
315
316 m_localFile = ::open(m_path.toLocal8Bit().constData(), O_RDONLY);
317 if (m_localFile == -1)
318 {
319 LOG(VB_FILE, LOG_ERR, QString("RemoteFile::Open(%1) Error: %2")
320 .arg(m_path, strerror(errno)));
321 return false;
322 }
323 return true;
324 }
326 if (!m_controlSock)
327 return false;
328
329 m_sock = openSocket(false);
330 if (!m_sock)
331 {
332 // Close the sockets if we received an error so that isOpen() will
333 // return false if the caller tries to use the RemoteFile.
334 Close(true);
335 return false;
336 }
337 m_canResume = true;
338
339 return true;
340}
341
342bool RemoteFile::ReOpen(const QString& newFilename)
343{
344 if (isLocal())
345 {
346 if (isOpen())
347 {
348 Close();
349 }
350 m_path = newFilename;
351 return Open();
352 }
353
354 QMutexLocker locker(&m_lock);
355
356 if (!CheckConnection(false))
357 {
358 LOG(VB_NETWORK, LOG_ERR, "RemoteFile::ReOpen(): Couldn't connect");
359 return false;
360 }
361
362 QStringList strlist( m_query.arg(m_recorderNum) );
363 strlist << "REOPEN";
364 strlist << newFilename;
365
367
368 m_lock.unlock();
369
370 bool retval = false;
371 if (!strlist.isEmpty())
372 retval = (strlist[0].toInt() != 0);
373
374 return retval;
375}
376
377void RemoteFile::Close(bool haslock)
378{
379 if (isLocal())
380 {
381 if (m_localFile >= 0)
383 m_localFile = -1;
384 delete m_fileWriter;
385 m_fileWriter = nullptr;
386 return;
387 }
388 if (!m_controlSock)
389 return;
390
391 QStringList strlist( m_query.arg(m_recorderNum) );
392 strlist << "DONE";
393
394 if (!haslock)
395 {
396 m_lock.lock();
397 }
399 strlist, 0, MythSocket::kShortTimeout))
400 {
401 LOG(VB_GENERAL, LOG_ERR, "Remote file timeout.");
402 }
403
404 if (m_sock)
405 {
406 m_sock->DecrRef();
407 m_sock = nullptr;
408 }
409 if (m_controlSock)
410 {
412 m_controlSock = nullptr;
413 }
414
415 if (!haslock)
416 {
417 m_lock.unlock();
418 }
419}
420
421bool RemoteFile::DeleteFile(const QString &url)
422{
423 if (isLocal(url))
424 {
425 QFile file(url);
426 return file.remove();
427 }
428
429 bool result = false;
430 QUrl qurl(url);
431 QString filename = qurl.path();
432 QString sgroup = qurl.userName();
433
434 if (!qurl.fragment().isEmpty() || url.endsWith("#"))
435 filename = filename + "#" + qurl.fragment();
436
437 if (filename.startsWith("/"))
438 filename = filename.right(filename.length()-1);
439
440 if (filename.isEmpty() || sgroup.isEmpty())
441 return false;
442
443 QStringList strlist("DELETE_FILE");
444 strlist << filename;
445 strlist << sgroup;
446
448
449 if (!strlist.isEmpty() && strlist[0] == "1")
450 result = true;
451
452 return result;
453}
454
455bool RemoteFile::Exists(const QString &url)
456{
457 if (url.isEmpty())
458 return false;
459
460 struct stat fileinfo {};
461 return Exists(url, &fileinfo);
462}
463
464bool RemoteFile::Exists(const QString &url, struct stat *fileinfo)
465{
466 if (url.isEmpty())
467 return false;
468
469 QUrl qurl(url);
470 QString filename = qurl.path();
471 QString sgroup = qurl.userName();
472 QString host = qurl.host();
473
474 if (isLocal(url) || gCoreContext->IsThisBackend(host))
475 {
476 LOG(VB_FILE, LOG_INFO,
477 QString("RemoteFile::Exists(): looking for local file: %1").arg(url));
478
479 bool fileExists = false;
480 QString fullFilePath = "";
481
482 if (url.startsWith("myth:"))
483 {
484 StorageGroup sGroup(sgroup, gCoreContext->GetHostName());
485 fullFilePath = sGroup.FindFile(filename);
486 if (!fullFilePath.isEmpty())
487 fileExists = true;
488 }
489 else
490 {
491 QFileInfo info(url);
492 fileExists = info.exists() /*&& info.isFile()*/;
493 fullFilePath = url;
494 }
495
496 if (fileExists)
497 {
498 if (stat(fullFilePath.toLocal8Bit().constData(), fileinfo) == -1)
499 {
500 LOG(VB_FILE, LOG_ERR,
501 QString("RemoteFile::Exists(): failed to stat file: %1").arg(fullFilePath) + ENO);
502 }
503 }
504
505 return fileExists;
506 }
507
508 LOG(VB_FILE, LOG_INFO,
509 QString("RemoteFile::Exists(): looking for remote file: %1").arg(url));
510
511 if (!qurl.fragment().isEmpty() || url.endsWith("#"))
512 filename = filename + "#" + qurl.fragment();
513
514 if (filename.startsWith("/"))
515 filename = filename.right(filename.length()-1);
516
517 if (filename.isEmpty())
518 return false;
519
520 QStringList strlist("QUERY_FILE_EXISTS");
521 strlist << filename;
522 if (!sgroup.isEmpty())
523 strlist << sgroup;
524
525 bool result = false;
526 if (RemoteSendReceiveStringList(host, strlist) && strlist[0] == "1")
527 {
528 if ((strlist.size() >= 15) && fileinfo)
529 {
530 fileinfo->st_dev = strlist[2].toLongLong();
531 fileinfo->st_ino = strlist[3].toLongLong();
532 fileinfo->st_mode = strlist[4].toLongLong();
533 fileinfo->st_nlink = strlist[5].toLongLong();
534 fileinfo->st_uid = strlist[6].toLongLong();
535 fileinfo->st_gid = strlist[7].toLongLong();
536 fileinfo->st_rdev = strlist[8].toLongLong();
537 fileinfo->st_size = strlist[9].toLongLong();
538#ifndef Q_OS_WINDOWS
539 fileinfo->st_blksize = strlist[10].toLongLong();
540 fileinfo->st_blocks = strlist[11].toLongLong();
541#endif
542 fileinfo->st_atime = strlist[12].toLongLong();
543 fileinfo->st_mtime = strlist[13].toLongLong();
544 fileinfo->st_ctime = strlist[14].toLongLong();
545 result = true;
546 }
547 else if (!fileinfo)
548 {
549 result = true;
550 }
551 }
552
553 return result;
554}
555
556QString RemoteFile::GetFileHash(const QString &url)
557{
558 if (isLocal(url))
559 {
560 return FileHash(url);
561 }
562 QString result;
563 QUrl qurl(url);
564 QString filename = qurl.path();
565 QString hostname = qurl.host();
566 QString sgroup = qurl.userName();
567
568 if (!qurl.fragment().isEmpty() || url.endsWith("#"))
569 filename = filename + "#" + qurl.fragment();
570
571 if (filename.startsWith("/"))
572 filename = filename.right(filename.length()-1);
573
574 if (filename.isEmpty() || sgroup.isEmpty())
575 return {};
576
577 QStringList strlist("QUERY_FILE_HASH");
578 strlist << filename;
579 strlist << sgroup;
580 strlist << hostname;
581
583 {
584 result = strlist[0];
585 }
586
587 return result;
588}
589
590bool RemoteFile::CopyFile (const QString& src, const QString& dst,
591 bool overwrite, bool verify)
592{
593 LOG(VB_FILE, LOG_INFO,
594 QString("RemoteFile::CopyFile: Copying file from '%1' to '%2'").arg(src, dst));
595
596 // sanity check
597 if (src == dst)
598 {
599 LOG(VB_GENERAL, LOG_ERR, "RemoteFile::CopyFile: Cannot copy a file to itself");
600 return false;
601 }
602
603 RemoteFile srcFile(src, false);
604 if (!srcFile.isOpen())
605 {
606 LOG(VB_GENERAL, LOG_ERR,
607 QString("RemoteFile::CopyFile: Failed to open file (%1) for reading.").arg(src));
608 return false;
609 }
610
611 const int readSize = 2 * 1024 * 1024;
612 char *buf = new char[readSize];
613 if (!buf)
614 {
615 LOG(VB_GENERAL, LOG_ERR, "RemoteFile::CopyFile: ERROR, unable to allocate copy buffer");
616 return false;
617 }
618
619 if (overwrite)
620 {
621 DeleteFile(dst);
622 }
623 else if (Exists(dst))
624 {
625 LOG(VB_GENERAL, LOG_ERR, "RemoteFile::CopyFile: File already exists");
626 delete[] buf;
627 return false;
628 }
629
630 RemoteFile dstFile(dst, true);
631 if (!dstFile.isOpen())
632 {
633 LOG(VB_GENERAL, LOG_ERR,
634 QString("RemoteFile::CopyFile: Failed to open file (%1) for writing.").arg(dst));
635 srcFile.Close();
636 delete[] buf;
637 return false;
638 }
639
640 dstFile.SetBlocking(true);
641
642 bool success = true;
643 int srcLen = 0;
644
645 while ((srcLen = srcFile.Read(buf, readSize)) > 0)
646 {
647 int dstLen = dstFile.Write(buf, srcLen);
648
649 if (dstLen == -1 || srcLen != dstLen)
650 {
651 LOG(VB_GENERAL, LOG_ERR,
652 "RemoteFile::CopyFile: Error while trying to write to destination file.");
653 success = false;
654 }
655 }
656
657 srcFile.Close();
658 dstFile.Close();
659 delete[] buf;
660
661 if (success && verify)
662 {
663 // Check written file is correct size
664 struct stat fileinfo {};
665 long long dstSize = Exists(dst, &fileinfo) ? fileinfo.st_size : -1;
666 long long srcSize = srcFile.GetFileSize();
667 if (dstSize != srcSize)
668 {
669 LOG(VB_GENERAL, LOG_ERR,
670 QString("RemoteFile::CopyFile: Copied file is wrong size (%1 rather than %2)")
671 .arg(dstSize).arg(srcSize));
672 success = false;
673 DeleteFile(dst);
674 }
675 }
676
677 return success;
678}
679
680bool RemoteFile::MoveFile (const QString& src, const QString& dst, bool overwrite)
681{
682 LOG(VB_FILE, LOG_INFO,
683 QString("RemoteFile::MoveFile: Moving file from '%1' to '%2'").arg(src, dst));
684
685 // sanity check
686 if (src == dst)
687 {
688 LOG(VB_GENERAL, LOG_ERR, "RemoteFile::MoveFile: Cannot move a file to itself");
689 return false;
690 }
691
692 if (isLocal(src) != isLocal(dst))
693 {
694 // Moving between local & remote requires a copy & delete
695 bool ok = CopyFile(src, dst, overwrite, true);
696 if (ok)
697 {
698 if (!DeleteFile(src))
699 LOG(VB_FILE, LOG_ERR,
700 "RemoteFile::MoveFile: Failed to delete file after successful copy");
701 }
702 return ok;
703 }
704
705 if (overwrite)
706 {
707 DeleteFile(dst);
708 }
709 else if (Exists(dst))
710 {
711 LOG(VB_GENERAL, LOG_ERR, "RemoteFile::MoveFile: File already exists");
712 return false;
713 }
714
715 if (isLocal(src))
716 {
717 // Moving local -> local
718 QFileInfo fi(dst);
719 if (QDir().mkpath(fi.path()) && QFile::rename(src, dst))
720 return true;
721
722 LOG(VB_FILE, LOG_ERR, "RemoteFile::MoveFile: Rename failed");
723 return false;
724 }
725
726 // Moving remote -> remote
727 QUrl srcUrl(src);
728 QUrl dstUrl(dst);
729
730 if (srcUrl.userName() != dstUrl.userName())
731 {
732 LOG(VB_FILE, LOG_ERR, "RemoteFile::MoveFile: Cannot change a file's Storage Group");
733 return false;
734 }
735
736 QStringList strlist("MOVE_FILE");
737 strlist << srcUrl.userName() << srcUrl.path() << dstUrl.path();
738
740
741 if (!strlist.isEmpty() && strlist[0] == "1")
742 return true;
743
744 LOG(VB_FILE, LOG_ERR, QString("RemoteFile::MoveFile: MOVE_FILE failed with: %1")
745 .arg(strlist.join(",")));
746 return false;
747}
748
750{
751 if (isLocal())
752 return;
753 QMutexLocker locker(&m_lock);
754 if (!m_sock)
755 {
756 LOG(VB_NETWORK, LOG_ERR, "RemoteFile::Reset(): Called with no socket");
757 return;
758 }
759 m_sock->Reset();
760}
761
762long long RemoteFile::Seek(long long pos, int whence, long long curpos)
763{
764 QMutexLocker locker(&m_lock);
765
766 return SeekInternal(pos, whence, curpos);
767}
768
769long long RemoteFile::SeekInternal(long long pos, int whence, long long curpos)
770{
771 if (isLocal())
772 {
773 if (!isOpen())
774 {
775 LOG(VB_FILE, LOG_ERR, "RemoteFile::Seek(): Called with no file opened");
776 return -1;
777 }
778 if (m_writeMode)
779 return m_fileWriter->Seek(pos, whence);
780
781 long long offset = 0LL;
782 if (whence == SEEK_SET)
783 {
784 QFileInfo info(m_path);
785 offset = std::min(pos, info.size());
786 }
787 else if (whence == SEEK_END)
788 {
789 QFileInfo info(m_path);
790 offset = info.size() + pos;
791 }
792 else if (whence == SEEK_CUR)
793 {
794 offset = ((curpos > 0) ? curpos : lseek(m_localFile, 0, SEEK_CUR)) + pos;
795 }
796 else
797 {
798 return -1;
799 }
800
801 off_t localpos = lseek(m_localFile, pos, whence);
802 if (localpos != pos)
803 {
804 LOG(VB_FILE, LOG_ERR,
805 QString("RemoteFile::Seek(): Couldn't seek to offset %1")
806 .arg(offset));
807 return -1;
808 }
809 return localpos;
810 }
811
812 if (!CheckConnection(false))
813 {
814 LOG(VB_NETWORK, LOG_ERR, "RemoteFile::Seek(): Couldn't connect");
815 return -1;
816 }
817
818 QStringList strlist( m_query.arg(m_recorderNum) );
819 strlist << "SEEK";
820 strlist << QString::number(pos);
821 strlist << QString::number(whence);
822 if (curpos > 0)
823 strlist << QString::number(curpos);
824 else
825 strlist << QString::number(m_readPosition);
826
827 bool ok = m_controlSock->SendReceiveStringList(strlist);
828
829 if (ok && !strlist.isEmpty())
830 {
831 m_lastPosition = m_readPosition = strlist[0].toLongLong();
832 m_sock->Reset();
833 return strlist[0].toLongLong();
834 }
835 m_lastPosition = 0LL;
836 return -1;
837}
838
839int RemoteFile::Write(const void *data, int size)
840{
841 int recv = 0;
842 int sent = 0;
843 unsigned zerocnt = 0;
844 bool error = false;
845 bool response = false;
846
847 if (!m_writeMode)
848 {
849 LOG(VB_NETWORK, LOG_ERR,
850 "RemoteFile::Write(): Called when not in write mode");
851 return -1;
852 }
853 if (isLocal())
854 {
855 if (!isOpen())
856 {
857 LOG(VB_FILE, LOG_ERR,
858 "RemoteFile::Write(): File not opened");
859 return -1;
860 }
861 return m_fileWriter->Write(data, size);
862 }
863
864 QMutexLocker locker(&m_lock);
865
866 if (!CheckConnection())
867 {
868 LOG(VB_NETWORK, LOG_ERR, "RemoteFile::Write(): Couldn't connect");
869 return -1;
870 }
871
872 QStringList strlist( m_query.arg(m_recorderNum) );
873 strlist << "WRITE_BLOCK";
874 strlist << QString::number(size);
875 bool ok = m_controlSock->WriteStringList(strlist);
876 if (!ok)
877 {
878 LOG(VB_NETWORK, LOG_ERR,
879 "RemoteFile::Write(): Block notification failed");
880 return -1;
881 }
882
883 recv = size;
884 while (sent < recv && !error && zerocnt++ < 50)
885 {
886 int ret = m_sock->Write((char*)data + sent, recv - sent);
887 if (ret > 0)
888 {
889 sent += ret;
890 }
891 else
892 {
893 LOG(VB_GENERAL, LOG_ERR, "RemoteFile::Write(): socket error");
894 error = true;
895 break;
896 }
897
900 !strlist.isEmpty())
901 {
902 recv = strlist[0].toInt(); // -1 on backend error
903 response = true;
904 }
905 }
906
907 if (!error && !response)
908 {
910 !strlist.isEmpty())
911 {
912 recv = strlist[0].toInt(); // -1 on backend error
913 }
914 else
915 {
916 LOG(VB_GENERAL, LOG_ERR,
917 "RemoteFile::Write(): No response from control socket.");
918 recv = -1;
919 }
920 }
921
922 LOG(VB_NETWORK, LOG_DEBUG,
923 QString("RemoteFile::Write(): reqd=%1, sent=%2, rept=%3, error=%4")
924 .arg(size).arg(sent).arg(recv).arg(error));
925
926 if (recv < 0)
927 return recv;
928
929 if (error || recv != sent)
930 {
931 sent = -1;
932 }
933 else
934 {
935 m_lastPosition += sent;
936 }
937
938 return sent;
939}
940
941int RemoteFile::Read(void *data, int size)
942{
943 int recv = 0;
944 int sent = 0;
945 bool error = false;
946 bool response = false;
947
948 QMutexLocker locker(&m_lock);
949
950 if (isLocal())
951 {
952 if (m_writeMode)
953 {
954 LOG(VB_FILE, LOG_ERR, "RemoteFile:Read() called in writing mode");
955 return -1;
956 }
957 if (isOpen())
958 {
959 return ::read(m_localFile, data, size);
960 }
961 LOG(VB_FILE, LOG_ERR, "RemoteFile:Read() called when local file not opened");
962 return -1;
963 }
964
965 if (!CheckConnection())
966 {
967 LOG(VB_NETWORK, LOG_ERR, "RemoteFile::Read(): Couldn't connect");
968 return -1;
969 }
970
971 if (m_sock->IsDataAvailable())
972 {
973 LOG(VB_NETWORK, LOG_ERR,
974 "RemoteFile::Read(): Read socket not empty to start!");
975 m_sock->Reset();
976 }
977
979 {
980 LOG(VB_NETWORK, LOG_WARNING,
981 "RemoteFile::Read(): Control socket not empty to start!");
983 }
984
985 QStringList strlist( m_query.arg(m_recorderNum) );
986 strlist << "REQUEST_BLOCK";
987 strlist << QString::number(size);
988 bool ok = m_controlSock->WriteStringList(strlist);
989 if (!ok)
990 {
991 LOG(VB_NETWORK, LOG_ERR, "RemoteFile::Read(): Block request failed");
992 return -1;
993 }
994
995 sent = size;
996
997 std::chrono::milliseconds waitms { 30ms };
998 MythTimer mtimer;
999 mtimer.start();
1000
1001 while (recv < sent && !error && mtimer.elapsed() < 10s)
1002 {
1003 int ret = m_sock->Read(((char *)data) + recv, sent - recv, waitms);
1004
1005 if (ret > 0)
1006 recv += ret;
1007 else if (ret < 0)
1008 error = true;
1009
1010 waitms += (waitms < 200ms) ? 20ms : 0ms;
1011
1014 !strlist.isEmpty())
1015 {
1016 sent = strlist[0].toInt(); // -1 on backend error
1017 response = true;
1018 if (ret < sent)
1019 {
1020 // We have received less than what the server sent, retry immediately
1021 ret = m_sock->Read(((char *)data) + recv, sent - recv, waitms);
1022 if (ret > 0)
1023 recv += ret;
1024 else if (ret < 0)
1025 error = true;
1026 }
1027 }
1028 }
1029
1030 if (!error && !response)
1031 {
1032 // Wait up to 1.5s for the backend to send the size
1033 // MythSocket::ReadString will drop the connection
1034 if (m_controlSock->ReadStringList(strlist, 1500ms) &&
1035 !strlist.isEmpty())
1036 {
1037 sent = strlist[0].toInt(); // -1 on backend error
1038 }
1039 else
1040 {
1041 LOG(VB_GENERAL, LOG_ERR,
1042 "RemoteFile::Read(): No response from control socket.");
1043 // If no data was received from control socket, and we got what we asked for
1044 // assume everything is okay
1045 if (recv == size)
1046 {
1047 sent = recv;
1048 }
1049 else
1050 {
1051 sent = -1;
1052 }
1053 // The TCP socket is dropped if there's a timeout, so we reconnect
1054 if (!Resume())
1055 {
1056 sent = -1;
1057 }
1058 }
1059 }
1060
1061 LOG(VB_NETWORK, LOG_DEBUG,
1062 QString("Read(): reqd=%1, rcvd=%2, rept=%3, error=%4")
1063 .arg(size).arg(recv).arg(sent).arg(error));
1064
1065 if (sent < 0)
1066 return sent;
1067
1068 if (error || sent != recv)
1069 {
1070 LOG(VB_GENERAL, LOG_WARNING,
1071 QString("RemoteFile::Read(): sent %1 != recv %2")
1072 .arg(sent).arg(recv));
1073 recv = -1;
1074
1075 // The TCP socket is dropped if there's a timeout, so we reconnect
1076 if (!Resume())
1077 {
1078 LOG(VB_GENERAL, LOG_WARNING, "RemoteFile::Read(): Resume failed.");
1079 }
1080 else
1081 {
1082 LOG(VB_GENERAL, LOG_NOTICE, "RemoteFile::Read(): Resume success.");
1083 }
1084 }
1085 else
1086 {
1087 m_lastPosition += recv;
1088 }
1089
1090 return recv;
1091}
1092
1098long long RemoteFile::GetFileSize(void) const
1099{
1100 if (isLocal())
1101 {
1102 if (isOpen() && m_writeMode)
1103 {
1105 }
1106 if (Exists(m_path))
1107 {
1108 QFileInfo info(m_path);
1109 return info.size();
1110 }
1111 return -1;
1112 }
1113
1114 QMutexLocker locker(&m_lock);
1115 return m_fileSize;
1116}
1117
1127{
1128 if (isLocal())
1129 {
1130 return GetFileSize();
1131 }
1132
1133 QMutexLocker locker(&m_lock);
1134
1135 if (m_completed ||
1137 {
1138 return m_fileSize;
1139 }
1140
1141 if (!CheckConnection())
1142 {
1143 // Can't establish a new connection, using system one
1144 struct stat fileinfo {};
1145
1146 if (Exists(m_path, &fileinfo))
1147 {
1148 m_fileSize = fileinfo.st_size;
1149 }
1150 return m_fileSize;
1151 }
1152
1153 QStringList strlist(m_query.arg(m_recorderNum));
1154 strlist << "REQUEST_SIZE";
1155
1156 bool ok = m_controlSock->SendReceiveStringList(strlist);
1157
1158 if (ok && !strlist.isEmpty())
1159 {
1160 bool validate = false;
1161 long long size = strlist[0].toLongLong(&validate);
1162
1163 if (validate)
1164 {
1165 if (strlist.count() >= 2)
1166 {
1167 m_completed = (strlist[1].toInt() != 0);
1168 }
1169 m_fileSize = size;
1170 }
1171 else
1172 {
1173 struct stat fileinfo {};
1174
1175 if (Exists(m_path, &fileinfo))
1176 {
1177 m_fileSize = fileinfo.st_size;
1178 }
1179 }
1181 return m_fileSize;
1182 }
1183
1184 return -1;
1185}
1186
1187bool RemoteFile::SaveAs(QByteArray &data)
1188{
1189 long long fs = GetRealFileSize();
1190
1191 if (fs < 0)
1192 return false;
1193
1194 data.resize(fs);
1195 Read(data.data(), fs);
1196
1197 return true;
1198}
1199
1201{
1202 if (isLocal())
1203 {
1204 // not much we can do with local accesses
1205 return;
1206 }
1207 if (m_timeoutIsFast == fast)
1208 return;
1209
1210 QMutexLocker locker(&m_lock);
1211
1212 // The m_controlSock variable is valid if the CheckConnection
1213 // function returns true. The local case has already been
1214 // handled. The CheckConnection function can call Resume which
1215 // calls Close, which deletes m_controlSock. However, the
1216 // subsequent call to OpenInternal is guaranteed to recreate the
1217 // socket or return false for a non-local connection, and this must
1218 // be a non-local connection if this line of code is executed.
1219 if (!CheckConnection())
1220 {
1221 LOG(VB_NETWORK, LOG_ERR,
1222 "RemoteFile::SetTimeout(): Couldn't connect");
1223 return;
1224 }
1225 if (m_controlSock == nullptr)
1226 return;
1227
1228 QStringList strlist( m_query.arg(m_recorderNum) );
1229 strlist << "SET_TIMEOUT";
1230 strlist << QString::number((int)fast);
1231
1233
1234 m_timeoutIsFast = fast;
1235}
1236
1237QDateTime RemoteFile::LastModified(const QString &url)
1238{
1239 if (isLocal(url))
1240 {
1241 QFileInfo info(url);
1242 return info.lastModified();
1243 }
1244 QDateTime result;
1245 QUrl qurl(url);
1246 QString filename = qurl.path();
1247 QString sgroup = qurl.userName();
1248
1249 if (!qurl.fragment().isEmpty() || url.endsWith("#"))
1250 filename = filename + "#" + qurl.fragment();
1251
1252 if (filename.startsWith("/"))
1253 filename = filename.right(filename.length()-1);
1254
1255 if (filename.isEmpty() || sgroup.isEmpty())
1256 return result;
1257
1258 QStringList strlist("QUERY_SG_FILEQUERY");
1259 strlist << qurl.host();
1260 strlist << sgroup;
1261 strlist << filename;
1262
1264
1265 if (strlist.size() > 1) {
1266 if (!strlist[1].isEmpty() && (strlist[1].toInt() != -1))
1267 result = MythDate::fromSecsSinceEpoch(strlist[1].toLongLong());
1268 else
1269 result = QDateTime();;
1270 }
1271
1272 return result;
1273}
1274
1275QDateTime RemoteFile::LastModified(void) const
1276{
1277 return LastModified(m_path);
1278}
1279
1289QString RemoteFile::FindFile(const QString& filename, const QString& host,
1290 const QString& storageGroup, bool useRegex,
1291 bool allowFallback)
1292{
1293 QStringList files = RemoteFile::FindFileList(filename, host, storageGroup, useRegex, allowFallback);
1294
1295 if (!files.isEmpty())
1296 return files[0];
1297
1298 return {};
1299}
1300
1310QStringList RemoteFile::FindFileList(const QString& filename, const QString& host,
1311 const QString& storageGroup, bool useRegex,
1312 bool allowFallback)
1313{
1314 LOG(VB_FILE, LOG_INFO, QString("RemoteFile::FindFile(): looking for '%1' on '%2' in group '%3' "
1315 "(useregex: %4, allowfallback: %5)")
1316 .arg(filename, host, storageGroup)
1317 .arg(useRegex).arg(allowFallback));
1318
1319 if (filename.isEmpty() || storageGroup.isEmpty())
1320 return {};
1321
1322 QStringList strList;
1323 QString hostName = host;
1324
1325 if (hostName.isEmpty())
1326 hostName = gCoreContext->GetMasterHostName();
1327
1328 // if we are looking for the file on this host just search the local storage group first
1329 if (gCoreContext->IsThisBackend(hostName))
1330 {
1331 // We could have made it this far with an IP when we really want
1332 // a hostname
1333 hostName = gCoreContext->GetHostName();
1334 StorageGroup sgroup(storageGroup, hostName);
1335
1336 if (useRegex)
1337 {
1338 QFileInfo fi(filename);
1339 QStringList files = sgroup.GetFileList('/' + fi.path());
1340
1341 LOG(VB_FILE, LOG_INFO, QString("RemoteFile::FindFileList: Looking in dir '%1' for '%2'")
1342 .arg(fi.path(), fi.fileName()));
1343
1344 for (int x = 0; x < files.size(); x++)
1345 {
1346 LOG(VB_FILE, LOG_INFO, QString("RemoteFile::FindFileList: Found '%1 - %2'")
1347 .arg(x).arg(files[x]));
1348 }
1349
1350 QStringList filteredFiles = files.filter(QRegularExpression(fi.fileName()));
1351 strList.reserve(filteredFiles.size());
1352 for (const QString& file : std::as_const(filteredFiles))
1353 {
1356 fi.path() + '/' + file,
1357 storageGroup);
1358 }
1359 }
1360 else
1361 {
1362 if (!sgroup.FindFile(filename).isEmpty())
1363 {
1364 strList << MythCoreContext::GenMythURL(hostName,
1366 filename, storageGroup);
1367 }
1368 }
1369
1370 if (!strList.isEmpty() || !allowFallback)
1371 return strList;
1372 }
1373
1374 // if we didn't find any files ask the master BE to find it
1375 if (strList.isEmpty() && !gCoreContext->IsMasterBackend())
1376 {
1377 strList << "QUERY_FINDFILE" << hostName << storageGroup << filename
1378 << (useRegex ? "1" : "0")
1379 << "1";
1380
1381 if (gCoreContext->SendReceiveStringList(strList))
1382 {
1383 if (!strList.empty() && !strList[0].isEmpty() &&
1384 strList[0] != "NOT FOUND" && !strList[0].startsWith("ERROR: "))
1385 return strList;
1386 }
1387 }
1388
1389 return {};
1390}
1391
1398{
1399 if (m_fileWriter)
1400 {
1401 return m_fileWriter->SetBlocking(block);
1402 }
1403 return true;
1404}
1405
1413{
1414 if (IsConnected())
1415 {
1416 return true;
1417 }
1418 if (!m_canResume)
1419 {
1420 return false;
1421 }
1422 return Resume(repos);
1423}
1424
1430{
1431 return m_sock && m_controlSock &&
1433}
1434
1440bool RemoteFile::Resume(bool repos)
1441{
1442 Close(true);
1443 if (!OpenInternal())
1444 return false;
1445
1446 if (repos)
1447 {
1449 if (SeekInternal(m_lastPosition, SEEK_SET) < 0)
1450 {
1451 Close(true);
1452 LOG(VB_FILE, LOG_ERR,
1453 QString("RemoteFile::Resume: Enable to re-seek into last known "
1454 "position (%1").arg(m_lastPosition));
1455 return false;
1456 }
1457 }
1459 return true;
1460}
1461
1462static QString downloadRemoteFile(const QString &cmd, const QString &url,
1463 const QString &storageGroup,
1464 const QString &filename)
1465{
1466 QStringList strlist(cmd);
1467 strlist << url;
1468 strlist << storageGroup;
1469 strlist << filename;
1470
1471 bool ok = gCoreContext->SendReceiveStringList(strlist);
1472
1473 if (!ok || strlist.size() < 2 || strlist[0] != "OK")
1474 {
1475 LOG(VB_GENERAL, LOG_ERR,
1476 "downloadRemoteFile(): " + cmd + " returned ERROR!");
1477 return {};
1478 }
1479
1480 return strlist[1];
1481}
1482
1483QString RemoteDownloadFile(const QString &url,
1484 const QString &storageGroup,
1485 const QString &filename)
1486{
1487 return downloadRemoteFile("DOWNLOAD_FILE", url, storageGroup, filename);
1488}
1489
1490QString RemoteDownloadFileNow(const QString &url,
1491 const QString &storageGroup,
1492 const QString &filename)
1493{
1494 return downloadRemoteFile("DOWNLOAD_FILE_NOW", url, storageGroup, filename);
1495}
1496
1497/* vim: set expandtab tabstop=4 shiftwidth=4: */
QString GetHostName(void)
MythSocket * ConnectCommandSocket(const QString &hostname, int port, const QString &announcement, bool *proto_mismatch=nullptr, int maxConnTry=-1, std::chrono::milliseconds setup_timeout=-1ms)
bool CheckProtoVersion(MythSocket *socket, std::chrono::milliseconds timeout=kMythSocketLongTimeout, bool error_dialog_desired=false)
int GetBackendServerPort(void)
Returns the locally defined backend control port.
bool IsThisBackend(const QString &addr)
is this address mapped to this backend host
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
QString GetBackendServerIP(void)
Returns the IP address of the locally defined backend IP.
QString GetMasterHostName(void)
bool IsMasterBackend(void)
is this the actual MBE process
Class for communcating between myth backends and frontends.
Definition: mythsocket.h:26
bool SendReceiveStringList(QStringList &list, uint min_reply_length=0, std::chrono::milliseconds timeoutMS=kLongTimeout)
Definition: mythsocket.cpp:331
bool ReadStringList(QStringList &list, std::chrono::milliseconds timeoutMS=kShortTimeout)
Definition: mythsocket.cpp:318
bool IsConnected(void) const
Definition: mythsocket.cpp:556
bool IsDataAvailable(void)
Definition: mythsocket.cpp:562
static constexpr std::chrono::milliseconds kShortTimeout
Definition: mythsocket.h:70
int Read(char *data, int size, std::chrono::milliseconds max_wait)
Definition: mythsocket.cpp:532
int Write(const char *data, int size)
Definition: mythsocket.cpp:519
bool WriteStringList(const QStringList &list)
Definition: mythsocket.cpp:306
void Reset(void)
Definition: mythsocket.cpp:546
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:14
std::chrono::milliseconds restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
Definition: mythtimer.cpp:62
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
bool isRunning(void) const
Returns true if start() or restart() has been called at least once since construction and since any c...
Definition: mythtimer.cpp:135
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
MythSocket * m_controlSock
Definition: remotefile.h:89
int m_localFile
Definition: remotefile.h:99
static bool CopyFile(const QString &src, const QString &dst, bool overwrite=false, bool verify=false)
Definition: remotefile.cpp:590
bool ReOpen(const QString &newFilename)
Definition: remotefile.cpp:342
bool isLocal(void) const
Definition: remotefile.cpp:117
int Read(void *data, int size)
Definition: remotefile.cpp:941
bool m_completed
Definition: remotefile.h:94
QStringList m_auxFiles
Definition: remotefile.h:98
bool OpenInternal(void)
Attempts to resume from a disconnected step.
Definition: remotefile.cpp:270
static QString FindFile(const QString &filename, const QString &host, const QString &storageGroup, bool useRegex=false, bool allowFallback=false)
Search all BE's for a file in the give storage group.
static QString GetFileHash(const QString &url)
Definition: remotefile.cpp:556
QStringList m_possibleAuxFiles
Definition: remotefile.h:97
void Close(bool haslock=false)
Definition: remotefile.cpp:377
static bool MoveFile(const QString &src, const QString &dst, bool overwrite=false)
Definition: remotefile.cpp:680
bool m_canResume
Definition: remotefile.h:85
long long Seek(long long pos, int whence, long long curpos=-1)
Definition: remotefile.cpp:762
long long GetRealFileSize(void)
GetRealFileSize: returns the current remote file's size.
ThreadedFileWriter * m_fileWriter
Definition: remotefile.h:100
static QStringList FindFileList(const QString &filename, const QString &host, const QString &storageGroup, bool useRegex=false, bool allowFallback=false)
Search all BE's for files in the give storage group.
QString m_query
Definition: remotefile.h:91
QMutex m_lock
Definition: remotefile.h:88
bool CheckConnection(bool repos=true)
Check current connection and re-establish it if lost.
void Reset(void)
Definition: remotefile.cpp:749
bool Open(void)
Definition: remotefile.cpp:257
QDateTime LastModified(void) const
long long m_lastPosition
Definition: remotefile.h:84
MythTimer m_lastSizeCheck
Definition: remotefile.h:95
bool m_writeMode
Definition: remotefile.h:93
std::chrono::milliseconds m_timeoutMs
Definition: remotefile.h:80
long long m_fileSize
Definition: remotefile.h:81
bool SaveAs(QByteArray &data)
bool m_useReadAhead
Definition: remotefile.h:79
bool m_timeoutIsFast
Definition: remotefile.h:82
long long m_readPosition
Definition: remotefile.h:83
bool SetBlocking(bool m_block=true)
Set write blocking mode for the ThreadedFileWriter instance.
MythSocket * openSocket(bool control)
Definition: remotefile.cpp:122
static bool DeleteFile(const QString &url)
Definition: remotefile.cpp:421
int m_recorderNum
Definition: remotefile.h:86
MythSocket * m_sock
Definition: remotefile.h:90
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:464
bool isOpen(void) const
Definition: remotefile.cpp:248
long long SeekInternal(long long pos, int whence, long long curpos=-1)
Definition: remotefile.cpp:769
QString m_path
Definition: remotefile.h:78
int Write(const void *data, int size)
Definition: remotefile.cpp:839
RemoteFile(QString url="", bool write=false, bool usereadahead=true, std::chrono::milliseconds timeout=2s, const QStringList *possibleAuxiliaryFiles=nullptr)
Definition: remotefile.cpp:71
bool IsConnected(void)
Check if both the control and data sockets are currently connected.
bool Resume(bool repos=true)
Attempts to resume from a disconnected step.
void SetTimeout(bool fast)
long long GetFileSize(void) const
GetFileSize: returns the remote file's size at the time it was first opened Will query the server in ...
QString FindFile(const QString &filename)
QStringList GetFileList(const QString &Path, bool recursive=false)
This class supports the writing of recordings to disk.
bool SetBlocking(bool block=true)
Set write blocking mode While in blocking mode, ThreadedFileWriter::Write will wait for buffers to be...
long long Seek(long long pos, int whence)
Seek to a position within stream; May be unsafe.
bool Open(void)
Opens the file we will be writing to.
void Flush(void)
Allow DiskLoop() to flush buffer completely ignoring low watermark.
int Write(const void *data, uint count)
Writes data to the end of the write buffer.
#define close
Definition: compat.h:28
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythDB * GetMythDB(void)
Definition: mythdb.cpp:50
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QString FileHash(const QString &filename)
MBASE_PUBLIC QDateTime fromSecsSinceEpoch(int64_t seconds)
This function takes the number of seconds since the start of the epoch and returns a QDateTime with t...
Definition: mythdate.cpp:81
dictionary info
Definition: azlyrics.py:7
def read(device=None, features=[])
Definition: disc.py:35
def error(message)
Definition: smolt.py:409
string hostname
Definition: caa.py:17
def write(text, progress=True)
Definition: mythburn.py:306
bool exists(str path)
Definition: xbmcvfs.py:51
QString RemoteDownloadFile(const QString &url, const QString &storageGroup, const QString &filename)
QString RemoteDownloadFileNow(const QString &url, const QString &storageGroup, const QString &filename)
static bool RemoteSendReceiveStringList(const QString &host, QStringList &strlist)
Definition: remotefile.cpp:34
static constexpr int8_t O_LARGEFILE
Definition: remotefile.cpp:17
static constexpr std::chrono::milliseconds MAX_FILE_CHECK
Definition: remotefile.cpp:32
static QString downloadRemoteFile(const QString &cmd, const QString &url, const QString &storageGroup, const QString &filename)