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