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