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.push_back(QString("ANN FileTransfer %1 %2 %3 %4")
189 .arg(hostname).arg(static_cast<int>(m_writeMode))
190 .arg(static_cast<int>(m_useReadAhead)).arg(m_timeoutMs.count()));
191 strlist << QString("%1").arg(dir);
192 strlist << sgroup;
193
194 for (const auto& fname : std::as_const(m_possibleAuxFiles))
195 strlist << fname;
196
197 if (!lsock->SendReceiveStringList(strlist))
198 {
199 LOG(VB_GENERAL, LOG_ERR, loc +
200 QString("Did not get proper response from %1:%2")
201 .arg(host).arg(port));
202 strlist.clear();
203 strlist.push_back("ERROR");
204 strlist.push_back("invalid response");
205 }
206
207 if (strlist.size() >= 3)
208 {
209 auto it = strlist.begin(); ++it;
210 m_recorderNum = (*it).toInt(); ++it;
211 m_fileSize = (*(it)).toLongLong(); ++it;
212 for (; it != strlist.end(); ++it)
213 m_auxFiles << *it;
214 }
215 else if (!strlist.isEmpty() && strlist.size() < 3 &&
216 strlist[0] != "ERROR")
217 {
218 LOG(VB_GENERAL, LOG_ERR, loc +
219 QString("Did not get proper response from %1:%2")
220 .arg(host).arg(port));
221 strlist.clear();
222 strlist.push_back("ERROR");
223 strlist.push_back("invalid response");
224 }
225 }
226
227 if (strlist.isEmpty() || strlist[0] == "ERROR")
228 {
229 lsock->DecrRef();
230 lsock = nullptr;
231 if (strlist.isEmpty())
232 {
233 LOG(VB_GENERAL, LOG_ERR, loc + "Failed to open socket, timeout");
234 }
235 else
236 {
237 LOG(VB_GENERAL, LOG_ERR, loc + "Failed to open socket" +
238 ((strlist.size() >= 2) ?
239 QString(", error was %1").arg(strlist[1]) :
240 QString(", remote error")));
241 }
242 }
243
244 return lsock;
245}
246
248{
249 if (isLocal())
250 {
251 return m_writeMode ? (m_fileWriter != nullptr) : (m_localFile != -1);
252 }
253 return m_sock && m_controlSock;
254}
255
257{
258 if (isOpen())
259 return true;
260
261 QMutexLocker locker(&m_lock);
262 return OpenInternal();
263}
264
270{
271 if (isLocal())
272 {
273 if (m_writeMode)
274 {
275 // make sure the directories are created if necessary
276 QFileInfo fi(m_path);
277 QDir dir(fi.path());
278 if (!dir.exists())
279 {
280 LOG(VB_FILE, LOG_WARNING, QString("RemoteFile::Open(%1) creating directories")
281 .arg(m_path));
282
283 if (!dir.mkpath(fi.path()))
284 {
285 LOG(VB_GENERAL, LOG_ERR, QString("RemoteFile::Open(%1) failed to create the directories")
286 .arg(m_path));
287 return false;
288 }
289 }
290
292 O_WRONLY|O_TRUNC|O_CREAT|O_LARGEFILE,
293 0644);
294
295 if (!m_fileWriter->Open())
296 {
297 delete m_fileWriter;
298 m_fileWriter = nullptr;
299 LOG(VB_FILE, LOG_ERR, QString("RemoteFile::Open(%1) write mode error")
300 .arg(m_path));
301 return false;
302 }
303 SetBlocking();
304 return true;
305 }
306
307 // local mode, read only
308 if (!Exists(m_path))
309 {
310 LOG(VB_FILE, LOG_ERR,
311 QString("RemoteFile::Open(%1) Error: Does not exist").arg(m_path));
312 return false;
313 }
314
315 m_localFile = ::open(m_path.toLocal8Bit().constData(), O_RDONLY);
316 if (m_localFile == -1)
317 {
318 LOG(VB_FILE, LOG_ERR, QString("RemoteFile::Open(%1) Error: %2")
319 .arg(m_path, strerror(errno)));
320 return false;
321 }
322 return true;
323 }
325 if (!m_controlSock)
326 return false;
327
328 m_sock = openSocket(false);
329 if (!m_sock)
330 {
331 // Close the sockets if we received an error so that isOpen() will
332 // return false if the caller tries to use the RemoteFile.
333 Close(true);
334 return false;
335 }
336 m_canResume = true;
337
338 return true;
339}
340
341bool RemoteFile::ReOpen(const QString& newFilename)
342{
343 if (isLocal())
344 {
345 if (isOpen())
346 {
347 Close();
348 }
349 m_path = newFilename;
350 return Open();
351 }
352
353 QMutexLocker locker(&m_lock);
354
355 if (!CheckConnection(false))
356 {
357 LOG(VB_NETWORK, LOG_ERR, "RemoteFile::ReOpen(): Couldn't connect");
358 return false;
359 }
360
361 QStringList strlist( m_query.arg(m_recorderNum) );
362 strlist << "REOPEN";
363 strlist << newFilename;
364
366
367 m_lock.unlock();
368
369 bool retval = false;
370 if (!strlist.isEmpty())
371 retval = (strlist[0].toInt() != 0);
372
373 return retval;
374}
375
376void RemoteFile::Close(bool haslock)
377{
378 if (isLocal())
379 {
380 if (m_localFile >= 0)
382 m_localFile = -1;
383 delete m_fileWriter;
384 m_fileWriter = nullptr;
385 return;
386 }
387 if (!m_controlSock)
388 return;
389
390 QStringList strlist( m_query.arg(m_recorderNum) );
391 strlist << "DONE";
392
393 if (!haslock)
394 {
395 m_lock.lock();
396 }
398 strlist, 0, MythSocket::kShortTimeout))
399 {
400 LOG(VB_GENERAL, LOG_ERR, "Remote file timeout.");
401 }
402
403 if (m_sock)
404 {
405 m_sock->DecrRef();
406 m_sock = nullptr;
407 }
408 if (m_controlSock)
409 {
411 m_controlSock = nullptr;
412 }
413
414 if (!haslock)
415 {
416 m_lock.unlock();
417 }
418}
419
420bool RemoteFile::DeleteFile(const QString &url)
421{
422 if (isLocal(url))
423 {
424 QFile file(url);
425 return file.remove();
426 }
427
428 bool result = false;
429 QUrl qurl(url);
430 QString filename = qurl.path();
431 QString sgroup = qurl.userName();
432
433 if (!qurl.fragment().isEmpty() || url.endsWith("#"))
434 filename = filename + "#" + qurl.fragment();
435
436 if (filename.startsWith("/"))
437 filename = filename.right(filename.length()-1);
438
439 if (filename.isEmpty() || sgroup.isEmpty())
440 return false;
441
442 QStringList strlist("DELETE_FILE");
443 strlist << filename;
444 strlist << sgroup;
445
447
448 if (!strlist.isEmpty() && strlist[0] == "1")
449 result = true;
450
451 return result;
452}
453
454bool RemoteFile::Exists(const QString &url)
455{
456 if (url.isEmpty())
457 return false;
458
459 struct stat fileinfo {};
460 return Exists(url, &fileinfo);
461}
462
463bool RemoteFile::Exists(const QString &url, struct stat *fileinfo)
464{
465 if (url.isEmpty())
466 return false;
467
468 QUrl qurl(url);
469 QString filename = qurl.path();
470 QString sgroup = qurl.userName();
471 QString host = qurl.host();
472
473 if (isLocal(url) || gCoreContext->IsThisBackend(host))
474 {
475 LOG(VB_FILE, LOG_INFO,
476 QString("RemoteFile::Exists(): looking for local file: %1").arg(url));
477
478 bool fileExists = false;
479 QString fullFilePath = "";
480
481 if (url.startsWith("myth:"))
482 {
483 StorageGroup sGroup(sgroup, gCoreContext->GetHostName());
484 fullFilePath = sGroup.FindFile(filename);
485 if (!fullFilePath.isEmpty())
486 fileExists = true;
487 }
488 else
489 {
490 QFileInfo info(url);
491 fileExists = info.exists() /*&& info.isFile()*/;
492 fullFilePath = url;
493 }
494
495 if (fileExists)
496 {
497 if (stat(fullFilePath.toLocal8Bit().constData(), fileinfo) == -1)
498 {
499 LOG(VB_FILE, LOG_ERR,
500 QString("RemoteFile::Exists(): failed to stat file: %1").arg(fullFilePath) + ENO);
501 }
502 }
503
504 return fileExists;
505 }
506
507 LOG(VB_FILE, LOG_INFO,
508 QString("RemoteFile::Exists(): looking for remote file: %1").arg(url));
509
510 if (!qurl.fragment().isEmpty() || url.endsWith("#"))
511 filename = filename + "#" + qurl.fragment();
512
513 if (filename.startsWith("/"))
514 filename = filename.right(filename.length()-1);
515
516 if (filename.isEmpty())
517 return false;
518
519 QStringList strlist("QUERY_FILE_EXISTS");
520 strlist << filename;
521 if (!sgroup.isEmpty())
522 strlist << sgroup;
523
524 bool result = false;
525 if (RemoteSendReceiveStringList(host, strlist) && strlist[0] == "1")
526 {
527 if ((strlist.size() >= 15) && fileinfo)
528 {
529 fileinfo->st_dev = strlist[2].toLongLong();
530 fileinfo->st_ino = strlist[3].toLongLong();
531 fileinfo->st_mode = strlist[4].toLongLong();
532 fileinfo->st_nlink = strlist[5].toLongLong();
533 fileinfo->st_uid = strlist[6].toLongLong();
534 fileinfo->st_gid = strlist[7].toLongLong();
535 fileinfo->st_rdev = strlist[8].toLongLong();
536 fileinfo->st_size = strlist[9].toLongLong();
537#ifndef Q_OS_WINDOWS
538 fileinfo->st_blksize = strlist[10].toLongLong();
539 fileinfo->st_blocks = strlist[11].toLongLong();
540#endif
541 fileinfo->st_atime = strlist[12].toLongLong();
542 fileinfo->st_mtime = strlist[13].toLongLong();
543 fileinfo->st_ctime = strlist[14].toLongLong();
544 result = true;
545 }
546 else if (!fileinfo)
547 {
548 result = true;
549 }
550 }
551
552 return result;
553}
554
555QString RemoteFile::GetFileHash(const QString &url)
556{
557 if (isLocal(url))
558 {
559 return FileHash(url);
560 }
561 QString result;
562 QUrl qurl(url);
563 QString filename = qurl.path();
564 QString hostname = qurl.host();
565 QString sgroup = qurl.userName();
566
567 if (!qurl.fragment().isEmpty() || url.endsWith("#"))
568 filename = filename + "#" + qurl.fragment();
569
570 if (filename.startsWith("/"))
571 filename = filename.right(filename.length()-1);
572
573 if (filename.isEmpty() || sgroup.isEmpty())
574 return {};
575
576 QStringList strlist("QUERY_FILE_HASH");
577 strlist << filename;
578 strlist << sgroup;
579 strlist << hostname;
580
582 {
583 result = strlist[0];
584 }
585
586 return result;
587}
588
589bool RemoteFile::CopyFile (const QString& src, const QString& dst,
590 bool overwrite, bool verify)
591{
592 LOG(VB_FILE, LOG_INFO,
593 QString("RemoteFile::CopyFile: Copying file from '%1' to '%2'").arg(src, dst));
594
595 // sanity check
596 if (src == dst)
597 {
598 LOG(VB_GENERAL, LOG_ERR, "RemoteFile::CopyFile: Cannot copy a file to itself");
599 return false;
600 }
601
602 RemoteFile srcFile(src, false);
603 if (!srcFile.isOpen())
604 {
605 LOG(VB_GENERAL, LOG_ERR,
606 QString("RemoteFile::CopyFile: Failed to open file (%1) for reading.").arg(src));
607 return false;
608 }
609
610 const int readSize = 2 * 1024 * 1024;
611 char *buf = new char[readSize];
612 if (!buf)
613 {
614 LOG(VB_GENERAL, LOG_ERR, "RemoteFile::CopyFile: ERROR, unable to allocate copy buffer");
615 return false;
616 }
617
618 if (overwrite)
619 {
620 DeleteFile(dst);
621 }
622 else if (Exists(dst))
623 {
624 LOG(VB_GENERAL, LOG_ERR, "RemoteFile::CopyFile: File already exists");
625 delete[] buf;
626 return false;
627 }
628
629 RemoteFile dstFile(dst, true);
630 if (!dstFile.isOpen())
631 {
632 LOG(VB_GENERAL, LOG_ERR,
633 QString("RemoteFile::CopyFile: Failed to open file (%1) for writing.").arg(dst));
634 srcFile.Close();
635 delete[] buf;
636 return false;
637 }
638
639 dstFile.SetBlocking(true);
640
641 bool success = true;
642 int srcLen = 0;
643
644 while ((srcLen = srcFile.Read(buf, readSize)) > 0)
645 {
646 int dstLen = dstFile.Write(buf, srcLen);
647
648 if (dstLen == -1 || srcLen != dstLen)
649 {
650 LOG(VB_GENERAL, LOG_ERR,
651 "RemoteFile::CopyFile: Error while trying to write to destination file.");
652 success = false;
653 }
654 }
655
656 srcFile.Close();
657 dstFile.Close();
658 delete[] buf;
659
660 if (success && verify)
661 {
662 // Check written file is correct size
663 struct stat fileinfo {};
664 long long dstSize = Exists(dst, &fileinfo) ? fileinfo.st_size : -1;
665 long long srcSize = srcFile.GetFileSize();
666 if (dstSize != srcSize)
667 {
668 LOG(VB_GENERAL, LOG_ERR,
669 QString("RemoteFile::CopyFile: Copied file is wrong size (%1 rather than %2)")
670 .arg(dstSize).arg(srcSize));
671 success = false;
672 DeleteFile(dst);
673 }
674 }
675
676 return success;
677}
678
679bool RemoteFile::MoveFile (const QString& src, const QString& dst, bool overwrite)
680{
681 LOG(VB_FILE, LOG_INFO,
682 QString("RemoteFile::MoveFile: Moving file from '%1' to '%2'").arg(src, dst));
683
684 // sanity check
685 if (src == dst)
686 {
687 LOG(VB_GENERAL, LOG_ERR, "RemoteFile::MoveFile: Cannot move a file to itself");
688 return false;
689 }
690
691 if (isLocal(src) != isLocal(dst))
692 {
693 // Moving between local & remote requires a copy & delete
694 bool ok = CopyFile(src, dst, overwrite, true);
695 if (ok)
696 {
697 if (!DeleteFile(src))
698 LOG(VB_FILE, LOG_ERR,
699 "RemoteFile::MoveFile: Failed to delete file after successful copy");
700 }
701 return ok;
702 }
703
704 if (overwrite)
705 {
706 DeleteFile(dst);
707 }
708 else if (Exists(dst))
709 {
710 LOG(VB_GENERAL, LOG_ERR, "RemoteFile::MoveFile: File already exists");
711 return false;
712 }
713
714 if (isLocal(src))
715 {
716 // Moving local -> local
717 QFileInfo fi(dst);
718 if (QDir().mkpath(fi.path()) && QFile::rename(src, dst))
719 return true;
720
721 LOG(VB_FILE, LOG_ERR, "RemoteFile::MoveFile: Rename failed");
722 return false;
723 }
724
725 // Moving remote -> remote
726 QUrl srcUrl(src);
727 QUrl dstUrl(dst);
728
729 if (srcUrl.userName() != dstUrl.userName())
730 {
731 LOG(VB_FILE, LOG_ERR, "RemoteFile::MoveFile: Cannot change a file's Storage Group");
732 return false;
733 }
734
735 QStringList strlist("MOVE_FILE");
736 strlist << srcUrl.userName() << srcUrl.path() << dstUrl.path();
737
739
740 if (!strlist.isEmpty() && strlist[0] == "1")
741 return true;
742
743 LOG(VB_FILE, LOG_ERR, QString("RemoteFile::MoveFile: MOVE_FILE failed with: %1")
744 .arg(strlist.join(",")));
745 return false;
746}
747
749{
750 if (isLocal())
751 return;
752 QMutexLocker locker(&m_lock);
753 if (!m_sock)
754 {
755 LOG(VB_NETWORK, LOG_ERR, "RemoteFile::Reset(): Called with no socket");
756 return;
757 }
758 m_sock->Reset();
759}
760
761long long RemoteFile::Seek(long long pos, int whence, long long curpos)
762{
763 QMutexLocker locker(&m_lock);
764
765 return SeekInternal(pos, whence, curpos);
766}
767
768long long RemoteFile::SeekInternal(long long pos, int whence, long long curpos)
769{
770 if (isLocal())
771 {
772 if (!isOpen())
773 {
774 LOG(VB_FILE, LOG_ERR, "RemoteFile::Seek(): Called with no file opened");
775 return -1;
776 }
777 if (m_writeMode)
778 return m_fileWriter->Seek(pos, whence);
779
780 long long offset = 0LL;
781 if (whence == SEEK_SET)
782 {
783 QFileInfo info(m_path);
784 offset = std::min(pos, info.size());
785 }
786 else if (whence == SEEK_END)
787 {
788 QFileInfo info(m_path);
789 offset = info.size() + pos;
790 }
791 else if (whence == SEEK_CUR)
792 {
793 offset = ((curpos > 0) ? curpos : lseek(m_localFile, 0, SEEK_CUR)) + pos;
794 }
795 else
796 {
797 return -1;
798 }
799
800 off_t localpos = lseek(m_localFile, pos, whence);
801 if (localpos != pos)
802 {
803 LOG(VB_FILE, LOG_ERR,
804 QString("RemoteFile::Seek(): Couldn't seek to offset %1")
805 .arg(offset));
806 return -1;
807 }
808 return localpos;
809 }
810
811 if (!CheckConnection(false))
812 {
813 LOG(VB_NETWORK, LOG_ERR, "RemoteFile::Seek(): Couldn't connect");
814 return -1;
815 }
816
817 QStringList strlist( m_query.arg(m_recorderNum) );
818 strlist << "SEEK";
819 strlist << QString::number(pos);
820 strlist << QString::number(whence);
821 if (curpos > 0)
822 strlist << QString::number(curpos);
823 else
824 strlist << QString::number(m_readPosition);
825
826 bool ok = m_controlSock->SendReceiveStringList(strlist);
827
828 if (ok && !strlist.isEmpty())
829 {
830 m_lastPosition = m_readPosition = strlist[0].toLongLong();
831 m_sock->Reset();
832 return strlist[0].toLongLong();
833 }
834 m_lastPosition = 0LL;
835 return -1;
836}
837
838int RemoteFile::Write(const void *data, int size)
839{
840 int recv = 0;
841 int sent = 0;
842 unsigned zerocnt = 0;
843 bool error = false;
844 bool response = false;
845
846 if (!m_writeMode)
847 {
848 LOG(VB_NETWORK, LOG_ERR,
849 "RemoteFile::Write(): Called when not in write mode");
850 return -1;
851 }
852 if (isLocal())
853 {
854 if (!isOpen())
855 {
856 LOG(VB_FILE, LOG_ERR,
857 "RemoteFile::Write(): File not opened");
858 return -1;
859 }
860 return m_fileWriter->Write(data, size);
861 }
862
863 QMutexLocker locker(&m_lock);
864
865 if (!CheckConnection())
866 {
867 LOG(VB_NETWORK, LOG_ERR, "RemoteFile::Write(): Couldn't connect");
868 return -1;
869 }
870
871 QStringList strlist( m_query.arg(m_recorderNum) );
872 strlist << "WRITE_BLOCK";
873 strlist << QString::number(size);
874 bool ok = m_controlSock->WriteStringList(strlist);
875 if (!ok)
876 {
877 LOG(VB_NETWORK, LOG_ERR,
878 "RemoteFile::Write(): Block notification failed");
879 return -1;
880 }
881
882 recv = size;
883 while (sent < recv && !error && zerocnt++ < 50)
884 {
885 int ret = m_sock->Write((char*)data + sent, recv - sent);
886 if (ret > 0)
887 {
888 sent += ret;
889 }
890 else
891 {
892 LOG(VB_GENERAL, LOG_ERR, "RemoteFile::Write(): socket error");
893 error = true;
894 break;
895 }
896
899 !strlist.isEmpty())
900 {
901 recv = strlist[0].toInt(); // -1 on backend error
902 response = true;
903 }
904 }
905
906 if (!error && !response)
907 {
909 !strlist.isEmpty())
910 {
911 recv = strlist[0].toInt(); // -1 on backend error
912 }
913 else
914 {
915 LOG(VB_GENERAL, LOG_ERR,
916 "RemoteFile::Write(): No response from control socket.");
917 recv = -1;
918 }
919 }
920
921 LOG(VB_NETWORK, LOG_DEBUG,
922 QString("RemoteFile::Write(): reqd=%1, sent=%2, rept=%3, error=%4")
923 .arg(size).arg(sent).arg(recv).arg(error));
924
925 if (recv < 0)
926 return recv;
927
928 if (error || recv != sent)
929 {
930 sent = -1;
931 }
932 else
933 {
934 m_lastPosition += sent;
935 }
936
937 return sent;
938}
939
940int RemoteFile::Read(void *data, int size)
941{
942 int recv = 0;
943 int sent = 0;
944 bool error = false;
945 bool response = false;
946
947 QMutexLocker locker(&m_lock);
948
949 if (isLocal())
950 {
951 if (m_writeMode)
952 {
953 LOG(VB_FILE, LOG_ERR, "RemoteFile:Read() called in writing mode");
954 return -1;
955 }
956 if (isOpen())
957 {
958 return ::read(m_localFile, data, size);
959 }
960 LOG(VB_FILE, LOG_ERR, "RemoteFile:Read() called when local file not opened");
961 return -1;
962 }
963
964 if (!CheckConnection())
965 {
966 LOG(VB_NETWORK, LOG_ERR, "RemoteFile::Read(): Couldn't connect");
967 return -1;
968 }
969
970 if (m_sock->IsDataAvailable())
971 {
972 LOG(VB_NETWORK, LOG_ERR,
973 "RemoteFile::Read(): Read socket not empty to start!");
974 m_sock->Reset();
975 }
976
978 {
979 LOG(VB_NETWORK, LOG_WARNING,
980 "RemoteFile::Read(): Control socket not empty to start!");
982 }
983
984 QStringList strlist( m_query.arg(m_recorderNum) );
985 strlist << "REQUEST_BLOCK";
986 strlist << QString::number(size);
987 bool ok = m_controlSock->WriteStringList(strlist);
988 if (!ok)
989 {
990 LOG(VB_NETWORK, LOG_ERR, "RemoteFile::Read(): Block request failed");
991 return -1;
992 }
993
994 sent = size;
995
996 std::chrono::milliseconds waitms { 30ms };
997 MythTimer mtimer;
998 mtimer.start();
999
1000 while (recv < sent && !error && mtimer.elapsed() < 10s)
1001 {
1002 int ret = m_sock->Read(((char *)data) + recv, sent - recv, waitms);
1003
1004 if (ret > 0)
1005 recv += ret;
1006 else if (ret < 0)
1007 error = true;
1008
1009 waitms += (waitms < 200ms) ? 20ms : 0ms;
1010
1013 !strlist.isEmpty())
1014 {
1015 sent = strlist[0].toInt(); // -1 on backend error
1016 response = true;
1017 if (ret < sent)
1018 {
1019 // We have received less than what the server sent, retry immediately
1020 ret = m_sock->Read(((char *)data) + recv, sent - recv, waitms);
1021 if (ret > 0)
1022 recv += ret;
1023 else if (ret < 0)
1024 error = true;
1025 }
1026 }
1027 }
1028
1029 if (!error && !response)
1030 {
1031 // Wait up to 1.5s for the backend to send the size
1032 // MythSocket::ReadString will drop the connection
1033 if (m_controlSock->ReadStringList(strlist, 1500ms) &&
1034 !strlist.isEmpty())
1035 {
1036 sent = strlist[0].toInt(); // -1 on backend error
1037 }
1038 else
1039 {
1040 LOG(VB_GENERAL, LOG_ERR,
1041 "RemoteFile::Read(): No response from control socket.");
1042 // If no data was received from control socket, and we got what we asked for
1043 // assume everything is okay
1044 if (recv == size)
1045 {
1046 sent = recv;
1047 }
1048 else
1049 {
1050 sent = -1;
1051 }
1052 // The TCP socket is dropped if there's a timeout, so we reconnect
1053 if (!Resume())
1054 {
1055 sent = -1;
1056 }
1057 }
1058 }
1059
1060 LOG(VB_NETWORK, LOG_DEBUG,
1061 QString("Read(): reqd=%1, rcvd=%2, rept=%3, error=%4")
1062 .arg(size).arg(recv).arg(sent).arg(error));
1063
1064 if (sent < 0)
1065 return sent;
1066
1067 if (error || sent != recv)
1068 {
1069 LOG(VB_GENERAL, LOG_WARNING,
1070 QString("RemoteFile::Read(): sent %1 != recv %2")
1071 .arg(sent).arg(recv));
1072 recv = -1;
1073
1074 // The TCP socket is dropped if there's a timeout, so we reconnect
1075 if (!Resume())
1076 {
1077 LOG(VB_GENERAL, LOG_WARNING, "RemoteFile::Read(): Resume failed.");
1078 }
1079 else
1080 {
1081 LOG(VB_GENERAL, LOG_NOTICE, "RemoteFile::Read(): Resume success.");
1082 }
1083 }
1084 else
1085 {
1086 m_lastPosition += recv;
1087 }
1088
1089 return recv;
1090}
1091
1097long long RemoteFile::GetFileSize(void) const
1098{
1099 if (isLocal())
1100 {
1101 if (isOpen() && m_writeMode)
1102 {
1104 }
1105 if (Exists(m_path))
1106 {
1107 QFileInfo info(m_path);
1108 return info.size();
1109 }
1110 return -1;
1111 }
1112
1113 QMutexLocker locker(&m_lock);
1114 return m_fileSize;
1115}
1116
1126{
1127 if (isLocal())
1128 {
1129 return GetFileSize();
1130 }
1131
1132 QMutexLocker locker(&m_lock);
1133
1134 if (m_completed ||
1136 {
1137 return m_fileSize;
1138 }
1139
1140 if (!CheckConnection())
1141 {
1142 // Can't establish a new connection, using system one
1143 struct stat fileinfo {};
1144
1145 if (Exists(m_path, &fileinfo))
1146 {
1147 m_fileSize = fileinfo.st_size;
1148 }
1149 return m_fileSize;
1150 }
1151
1152 QStringList strlist(m_query.arg(m_recorderNum));
1153 strlist << "REQUEST_SIZE";
1154
1155 bool ok = m_controlSock->SendReceiveStringList(strlist);
1156
1157 if (ok && !strlist.isEmpty())
1158 {
1159 bool validate = false;
1160 long long size = strlist[0].toLongLong(&validate);
1161
1162 if (validate)
1163 {
1164 if (strlist.count() >= 2)
1165 {
1166 m_completed = (strlist[1].toInt() != 0);
1167 }
1168 m_fileSize = size;
1169 }
1170 else
1171 {
1172 struct stat fileinfo {};
1173
1174 if (Exists(m_path, &fileinfo))
1175 {
1176 m_fileSize = fileinfo.st_size;
1177 }
1178 }
1180 return m_fileSize;
1181 }
1182
1183 return -1;
1184}
1185
1186bool RemoteFile::SaveAs(QByteArray &data)
1187{
1188 long long fs = GetRealFileSize();
1189
1190 if (fs < 0)
1191 return false;
1192
1193 data.resize(fs);
1194 Read(data.data(), fs);
1195
1196 return true;
1197}
1198
1200{
1201 if (isLocal())
1202 {
1203 // not much we can do with local accesses
1204 return;
1205 }
1206 if (m_timeoutIsFast == fast)
1207 return;
1208
1209 QMutexLocker locker(&m_lock);
1210
1211 // The m_controlSock variable is valid if the CheckConnection
1212 // function returns true. The local case has already been
1213 // handled. The CheckConnection function can call Resume which
1214 // calls Close, which deletes m_controlSock. However, the
1215 // subsequent call to OpenInternal is guaranteed to recreate the
1216 // socket or return false for a non-local connection, and this must
1217 // be a non-local connection if this line of code is executed.
1218 if (!CheckConnection())
1219 {
1220 LOG(VB_NETWORK, LOG_ERR,
1221 "RemoteFile::SetTimeout(): Couldn't connect");
1222 return;
1223 }
1224 if (m_controlSock == nullptr)
1225 return;
1226
1227 QStringList strlist( m_query.arg(m_recorderNum) );
1228 strlist << "SET_TIMEOUT";
1229 strlist << QString::number((int)fast);
1230
1232
1233 m_timeoutIsFast = fast;
1234}
1235
1236QDateTime RemoteFile::LastModified(const QString &url)
1237{
1238 if (isLocal(url))
1239 {
1240 QFileInfo info(url);
1241 return info.lastModified();
1242 }
1243 QDateTime result;
1244 QUrl qurl(url);
1245 QString filename = qurl.path();
1246 QString sgroup = qurl.userName();
1247
1248 if (!qurl.fragment().isEmpty() || url.endsWith("#"))
1249 filename = filename + "#" + qurl.fragment();
1250
1251 if (filename.startsWith("/"))
1252 filename = filename.right(filename.length()-1);
1253
1254 if (filename.isEmpty() || sgroup.isEmpty())
1255 return result;
1256
1257 QStringList strlist("QUERY_SG_FILEQUERY");
1258 strlist << qurl.host();
1259 strlist << sgroup;
1260 strlist << filename;
1261
1263
1264 if (strlist.size() > 1) {
1265 if (!strlist[1].isEmpty() && (strlist[1].toInt() != -1))
1266 result = MythDate::fromSecsSinceEpoch(strlist[1].toLongLong());
1267 else
1268 result = QDateTime();;
1269 }
1270
1271 return result;
1272}
1273
1274QDateTime RemoteFile::LastModified(void) const
1275{
1276 return LastModified(m_path);
1277}
1278
1288QString RemoteFile::FindFile(const QString& filename, const QString& host,
1289 const QString& storageGroup, bool useRegex,
1290 bool allowFallback)
1291{
1292 QStringList files = RemoteFile::FindFileList(filename, host, storageGroup, useRegex, allowFallback);
1293
1294 if (!files.isEmpty())
1295 return files[0];
1296
1297 return {};
1298}
1299
1309QStringList RemoteFile::FindFileList(const QString& filename, const QString& host,
1310 const QString& storageGroup, bool useRegex,
1311 bool allowFallback)
1312{
1313 LOG(VB_FILE, LOG_INFO, QString("RemoteFile::FindFile(): looking for '%1' on '%2' in group '%3' "
1314 "(useregex: %4, allowfallback: %5)")
1315 .arg(filename, host, storageGroup)
1316 .arg(useRegex).arg(allowFallback));
1317
1318 if (filename.isEmpty() || storageGroup.isEmpty())
1319 return {};
1320
1321 QStringList strList;
1322 QString hostName = host;
1323
1324 if (hostName.isEmpty())
1325 hostName = gCoreContext->GetMasterHostName();
1326
1327 // if we are looking for the file on this host just search the local storage group first
1328 if (gCoreContext->IsThisBackend(hostName))
1329 {
1330 // We could have made it this far with an IP when we really want
1331 // a hostname
1332 hostName = gCoreContext->GetHostName();
1333 StorageGroup sgroup(storageGroup, hostName);
1334
1335 if (useRegex)
1336 {
1337 QFileInfo fi(filename);
1338 QStringList files = sgroup.GetFileList('/' + fi.path());
1339
1340 LOG(VB_FILE, LOG_INFO, QString("RemoteFile::FindFileList: Looking in dir '%1' for '%2'")
1341 .arg(fi.path(), fi.fileName()));
1342
1343 for (int x = 0; x < files.size(); x++)
1344 {
1345 LOG(VB_FILE, LOG_INFO, QString("RemoteFile::FindFileList: Found '%1 - %2'")
1346 .arg(x).arg(files[x]));
1347 }
1348
1349 QStringList filteredFiles = files.filter(QRegularExpression(fi.fileName()));
1350 for (const QString& file : std::as_const(filteredFiles))
1351 {
1354 fi.path() + '/' + file,
1355 storageGroup);
1356 }
1357 }
1358 else
1359 {
1360 if (!sgroup.FindFile(filename).isEmpty())
1361 {
1362 strList << MythCoreContext::GenMythURL(hostName,
1364 filename, storageGroup);
1365 }
1366 }
1367
1368 if (!strList.isEmpty() || !allowFallback)
1369 return strList;
1370 }
1371
1372 // if we didn't find any files ask the master BE to find it
1373 if (strList.isEmpty() && !gCoreContext->IsMasterBackend())
1374 {
1375 strList << "QUERY_FINDFILE" << hostName << storageGroup << filename
1376 << (useRegex ? "1" : "0")
1377 << "1";
1378
1379 if (gCoreContext->SendReceiveStringList(strList))
1380 {
1381 if (!strList.empty() && !strList[0].isEmpty() &&
1382 strList[0] != "NOT FOUND" && !strList[0].startsWith("ERROR: "))
1383 return strList;
1384 }
1385 }
1386
1387 return {};
1388}
1389
1396{
1397 if (m_fileWriter)
1398 {
1399 return m_fileWriter->SetBlocking(block);
1400 }
1401 return true;
1402}
1403
1411{
1412 if (IsConnected())
1413 {
1414 return true;
1415 }
1416 if (!m_canResume)
1417 {
1418 return false;
1419 }
1420 return Resume(repos);
1421}
1422
1428{
1429 return m_sock && m_controlSock &&
1431}
1432
1438bool RemoteFile::Resume(bool repos)
1439{
1440 Close(true);
1441 if (!OpenInternal())
1442 return false;
1443
1444 if (repos)
1445 {
1447 if (SeekInternal(m_lastPosition, SEEK_SET) < 0)
1448 {
1449 Close(true);
1450 LOG(VB_FILE, LOG_ERR,
1451 QString("RemoteFile::Resume: Enable to re-seek into last known "
1452 "position (%1").arg(m_lastPosition));
1453 return false;
1454 }
1455 }
1457 return true;
1458}
1459
1460static QString downloadRemoteFile(const QString &cmd, const QString &url,
1461 const QString &storageGroup,
1462 const QString &filename)
1463{
1464 QStringList strlist(cmd);
1465 strlist << url;
1466 strlist << storageGroup;
1467 strlist << filename;
1468
1469 bool ok = gCoreContext->SendReceiveStringList(strlist);
1470
1471 if (!ok || strlist.size() < 2 || strlist[0] != "OK")
1472 {
1473 LOG(VB_GENERAL, LOG_ERR,
1474 "downloadRemoteFile(): " + cmd + " returned ERROR!");
1475 return {};
1476 }
1477
1478 return strlist[1];
1479}
1480
1481QString RemoteDownloadFile(const QString &url,
1482 const QString &storageGroup,
1483 const QString &filename)
1484{
1485 return downloadRemoteFile("DOWNLOAD_FILE", url, storageGroup, filename);
1486}
1487
1488QString RemoteDownloadFileNow(const QString &url,
1489 const QString &storageGroup,
1490 const QString &filename)
1491{
1492 return downloadRemoteFile("DOWNLOAD_FILE_NOW", url, storageGroup, filename);
1493}
1494
1495/* 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:589
bool ReOpen(const QString &newFilename)
Definition: remotefile.cpp:341
bool isLocal(void) const
Definition: remotefile.cpp:117
int Read(void *data, int size)
Definition: remotefile.cpp:940
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:269
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:555
QStringList m_possibleAuxFiles
Definition: remotefile.h:97
void Close(bool haslock=false)
Definition: remotefile.cpp:376
static bool MoveFile(const QString &src, const QString &dst, bool overwrite=false)
Definition: remotefile.cpp:679
bool m_canResume
Definition: remotefile.h:85
long long Seek(long long pos, int whence, long long curpos=-1)
Definition: remotefile.cpp:761
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:748
bool Open(void)
Definition: remotefile.cpp:256
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:420
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:463
bool isOpen(void) const
Definition: remotefile.cpp:247
long long SeekInternal(long long pos, int whence, long long curpos=-1)
Definition: remotefile.cpp:768
QString m_path
Definition: remotefile.h:78
int Write(const void *data, int size)
Definition: remotefile.cpp:838
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
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: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)