MythTV master
fileserverhandler.cpp
Go to the documentation of this file.
1
2#include <sys/types.h>
3#include <sys/stat.h>
4#include <unistd.h>
5
6#include <QtGlobal>
7#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
8#include <QtSystemDetection>
9#endif
10#include <QChar> // Fix Qt6 GCC SFINAE warning
11#include <QReadLocker>
12#include <QString>
13#include <QWriteLocker>
14#include <utility>
15
17#include "libmythbase/mythdb.h"
26
31
33
35{
36 // iterate through transfer list and close if
37 // socket matches connected transfer
38 {
39 QWriteLocker wlock(&m_ftLock);
40 QMap<int, FileTransfer*>::iterator i;
41 for (i = m_ftMap.begin(); i != m_ftMap.end(); ++i)
42 {
43 if ((*i)->GetSocket() == socket)
44 {
45 (*i)->DecrRef();
46 m_ftMap.remove(i.key());
47 return;
48 }
49 }
50 }
51
52 // iterate through file server list and close
53 // if socket matched connected server
54 {
55 QWriteLocker wlock(&m_fsLock);
56 QMap<QString, SocketHandler*>::iterator i;
57 for (i = m_fsMap.begin(); i != m_fsMap.end(); ++i)
58 {
59 if ((*i)->GetSocket() == socket)
60 {
61 (*i)->DecrRef();
62 m_fsMap.remove(i.key());
63 return;
64 }
65 }
66 }
67}
68
69QString FileServerHandler::LocalFilePath(const QString &path,
70 const QString &wantgroup)
71{
72 QString lpath = QString(path);
73
74 if (lpath.section('/', -2, -2) == "channels")
75 {
76 // This must be an icon request. Check channel.icon to be safe.
77 QString file = lpath.section('/', -1);
78 lpath = "";
79
81 query.prepare("SELECT icon FROM channel "
82 "WHERE icon LIKE :FILENAME ;");
83 query.bindValue(":FILENAME", QString("%/") + file);
84
85 if (query.exec() && query.next())
86 {
87 lpath = query.value(0).toString();
88 }
89 else
90 {
91 MythDB::DBError("Icon path", query);
92 }
93 }
94 else
95 {
96 lpath = lpath.section('/', -1);
97
98 QString fpath = lpath;
99 if (fpath.endsWith(".png"))
100 fpath = fpath.left(fpath.length() - 4);
101
102 ProgramInfo pginfo(fpath);
103 if (pginfo.GetChanID())
104 {
105 QString pburl = GetPlaybackURL(&pginfo);
106 if (pburl.startsWith("/"))
107 {
108 lpath = pburl.section('/', 0, -2) + "/" + lpath;
109 LOG(VB_FILE, LOG_INFO,
110 QString("Local file path: %1").arg(lpath));
111 }
112 else
113 {
114 LOG(VB_GENERAL, LOG_ERR,
115 QString("LocalFilePath unable to find local "
116 "path for '%1', found '%2' instead.")
117 .arg(lpath, pburl));
118 lpath = "";
119 }
120 }
121 else if (!lpath.isEmpty())
122 {
123 // For securities sake, make sure filename is really the pathless.
124 QString opath = lpath;
125 StorageGroup sgroup;
126
127 if (!wantgroup.isEmpty())
128 {
129 sgroup.Init(wantgroup);
130 lpath = QString(path);
131 }
132 else
133 {
134 lpath = QFileInfo(lpath).fileName();
135 }
136
137 QString tmpFile = sgroup.FindFile(lpath);
138 if (!tmpFile.isEmpty())
139 {
140 lpath = tmpFile;
141 LOG(VB_FILE, LOG_INFO,
142 QString("LocalFilePath(%1 '%2'), found through "
143 "exhaustive search at '%3'")
144 .arg(path, opath, lpath));
145 }
146 else
147 {
148 LOG(VB_GENERAL, LOG_ERR, QString("LocalFilePath unable to "
149 "find local path for '%1'.")
150 .arg(opath));
151 lpath = "";
152 }
153
154 }
155 else
156 {
157 lpath = "";
158 }
159 }
160
161 return lpath;
162}
163
165{
166 if (deletethread != nullptr)
167 {
168 if (deletethread->isRunning())
169 return;
170
171 delete deletethread;
172 deletethread = nullptr;
173 }
174
177}
178
180 QStringList &commands, QStringList &slist)
181{
182 if (commands[1] == "FileServer")
183 {
184 if (slist.size() >= 3)
185 {
186 auto *handler = new SocketHandler(socket, m_parent, commands[2]);
187
188 handler->BlockShutdown(true);
189 handler->AllowStandardEvents(true);
190 handler->AllowSystemEvents(true);
191
192 handler->WriteStringList(QStringList("OK"));
193
194 QWriteLocker wlock(&m_fsLock);
195 m_fsMap.insert(commands[2], handler);
196 m_parent->AddSocketHandler(handler);
197
198 handler->DecrRef();
199
200 return true;
201 }
202 return false;
203 }
204
205 if (commands[1] != "FileTransfer")
206 return false;
207
208 if (slist.size() < 3)
209 return false;
210
211 if ((commands.size() < 3) || (commands.size() > 6))
212 return false;
213
214 FileTransfer *ft = nullptr;
215 QString hostname = "";
216 QString filename = "";
217 bool writemode = false;
218 bool usereadahead = true;
219 std::chrono::milliseconds timeout = 2s;
220 switch (commands.size())
221 {
222 case 6:
223 timeout = std::chrono::milliseconds(commands[5].toInt());
224 [[fallthrough]];
225 case 5:
226 usereadahead = (commands[4].toInt() != 0);
227 [[fallthrough]];
228 case 4:
229 writemode = (commands[3].toInt() != 0);
230 [[fallthrough]];
231 default:
232 hostname = commands[2];
233 }
234
235 QStringList::const_iterator it = slist.cbegin();
236 QString path = *(++it);
237 QString wantgroup = *(++it);
238
239 QStringList checkfiles;
240 while (++it != slist.cend())
241 checkfiles += *it;
242
243 slist.clear();
244
245 LOG(VB_GENERAL, LOG_DEBUG, "FileServerHandler::HandleAnnounce");
246 LOG(VB_GENERAL, LOG_INFO, QString("adding: %1 as remote file transfer")
247 .arg(hostname));
248
249 if (writemode)
250 {
251 if (wantgroup.isEmpty())
252 wantgroup = "Default";
253
254 StorageGroup sgroup(wantgroup, gCoreContext->GetHostName(), false);
255 QString dir = sgroup.FindNextDirMostFree();
256 if (dir.isEmpty())
257 {
258 LOG(VB_GENERAL, LOG_ERR, "Unable to determine directory "
259 "to write to in FileTransfer write command");
260
261 slist << "ERROR" << "filetransfer_directory_not_found";
262 socket->WriteStringList(slist);
263 return true;
264 }
265
266 if (path.isEmpty())
267 {
268 LOG(VB_GENERAL, LOG_ERR, QString("FileTransfer write "
269 "filename is empty in path '%1'.")
270 .arg(path));
271
272 slist << "ERROR" << "filetransfer_filename_empty";
273 socket->WriteStringList(slist);
274 return true;
275 }
276
277 if ((path.contains("/../")) ||
278 (path.startsWith("../")))
279 {
280 LOG(VB_GENERAL, LOG_ERR, QString("FileTransfer write "
281 "filename '%1' does not pass sanity checks.")
282 .arg(path));
283
284 slist << "ERROR" << "filetransfer_filename_dangerous";
285 socket->WriteStringList(slist);
286 return true;
287 }
288
289 filename = dir + "/" + path;
290 }
291 else
292 {
293 filename = LocalFilePath(path, wantgroup);
294 }
295
296 QFileInfo finfo(filename);
297 if (finfo.isDir())
298 {
299 LOG(VB_GENERAL, LOG_ERR, QString("FileTransfer filename "
300 "'%1' is actually a directory, cannot transfer.")
301 .arg(filename));
302
303 slist << "ERROR" << "filetransfer_filename_is_a_directory";
304 socket->WriteStringList(slist);
305 return true;
306 }
307
308 if (writemode)
309 {
310 QString dirPath = finfo.absolutePath();
311 QDir qdir(dirPath);
312 if (!qdir.exists())
313 {
314 if (!qdir.mkpath(dirPath))
315 {
316 LOG(VB_GENERAL, LOG_ERR, QString("FileTransfer "
317 "filename '%1' is in a subdirectory which does "
318 "not exist, but can not be created.")
319 .arg(filename));
320
321 slist << "ERROR" << "filetransfer_unable_to_create_subdirectory";
322 socket->WriteStringList(slist);
323 return true;
324 }
325 }
326
327 ft = new FileTransfer(filename, socket, m_parent, writemode);
328 }
329 else
330 {
331 ft = new FileTransfer(filename, socket, m_parent, usereadahead, timeout);
332 }
333
334 ft->BlockShutdown(true);
335
336 {
337 QWriteLocker wlock(&m_ftLock);
338 m_ftMap.insert(socket->GetSocketDescriptor(), ft);
339 }
340
341 slist << "OK"
342 << QString::number(socket->GetSocketDescriptor())
343 << QString::number(ft->GetFileSize());
344
345 if (!checkfiles.empty())
346 {
347 QFileInfo fi(filename);
348 QDir dir = fi.absoluteDir();
349 for (const auto & file : std::as_const(checkfiles))
350 {
351 if (dir.exists(file) &&
352 QFileInfo(dir, file).size() >= kReadTestSize)
353 slist << file;
354 }
355 }
356
357 socket->WriteStringList(slist);
359 ft->DecrRef(); ft = nullptr;
360
361 return true;
362}
363
365 QStringList &commands, QStringList &slist)
366{
367 if (commands[1] == "SlaveBackend")
368 {
369 // were not going to handle these, but we still want to track them
370 // for commands that need access to these sockets
371 if (slist.size() >= 3)
372 {
373 SocketHandler *handler = m_parent->GetConnectionBySocket(socket);
374 if (handler == nullptr)
375 return;
376
377 QWriteLocker wlock(&m_fsLock);
378 m_fsMap.insert(commands[2], handler);
379 }
380 }
381
382}
383
384bool FileServerHandler::HandleQuery(SocketHandler *socket, QStringList &commands,
385 QStringList &slist)
386{
387 bool handled = false;
388 QString command = commands[0];
389
390 if (command == "QUERY_FILETRANSFER")
391 handled = HandleQueryFileTransfer(socket, commands, slist);
392 else if (command == "QUERY_FREE_SPACE")
393 handled = HandleQueryFreeSpace(socket);
394 else if (command == "QUERY_FREE_SPACE_LIST")
395 handled = HandleQueryFreeSpaceList(socket);
396 else if (command == "QUERY_FREE_SPACE_SUMMARY")
397 handled = HandleQueryFreeSpaceSummary(socket);
398 else if (command == "QUERY_CHECKFILE")
399 handled = HandleQueryCheckFile(socket, slist);
400 else if (command == "QUERY_FILE_EXISTS")
401 handled = HandleQueryFileExists(socket, slist);
402 else if (command == "QUERY_FILE_HASH")
403 handled = HandleQueryFileHash(socket, slist);
404 else if (command == "DELETE_FILE")
405 handled = HandleDeleteFile(socket, slist);
406 else if (command == "QUERY_SG_GETFILELIST")
407 handled = HandleGetFileList(socket, slist);
408 else if (command == "QUERY_SG_FILEQUERY")
409 handled = HandleFileQuery(socket, slist);
410 else if (command == "DOWNLOAD_FILE" || command == "DOWNLOAD_FILE_NOW")
411 handled = HandleDownloadFile(socket, slist);
412 return handled;
413}
414
416{
418 return true;
419}
420
422{
423 QStringList hosts;
424
426 for (const auto & disk : std::as_const(disks))
427 if (!hosts.contains(disk.getHostname()))
428 hosts << disk.getHostname();
429
430 // TODO: get max bitrate from encoderlink
431 FileSystemInfoManager::Consolidate(disks, true, 14000, hosts.join(","));
432
434 return true;
435}
436
438{
440 // TODO: get max bitrate from encoderlink
441 FileSystemInfoManager::Consolidate(disks, true, 14000, "FreeSpaceSummary");
442
443 socket->WriteStringList({QString::number(disks.back().getTotalSpace()),
444 QString::number(disks.back().getUsedSpace())});
445 return true;
446}
447
449{
450 const QString localHostName = gCoreContext->GetHostName(); // cache this
451 QStringList groups(StorageGroup::kSpecialGroups);
452 groups.removeAll("LiveTV");
453 QString specialGroups = groups.join("', '");
454
456 query.prepare(QString("SELECT MIN(id),dirname "
457 "FROM storagegroup "
458 "WHERE hostname = :HOSTNAME "
459 "AND groupname NOT IN ( '%1' ) "
460 "GROUP BY dirname;").arg(specialGroups));
461 query.bindValue(":HOSTNAME", localHostName);
462
463 FileSystemInfoList fsInfos;
464 if (query.exec() && query.isActive())
465 {
466 // If we don't have any dirs of our own, fallback to list of Default
467 // dirs since that is what StorageGroup::Init() does.
468 if (!query.size())
469 {
470 query.prepare("SELECT MIN(id),dirname "
471 "FROM storagegroup "
472 "WHERE groupname = :GROUP "
473 "GROUP BY dirname;");
474 query.bindValue(":GROUP", "Default");
475 if (!query.exec())
476 MythDB::DBError("BackendQueryFileSystems", query);
477 }
478
479 QMap<QString, bool> foundDirs;
480
481 while (query.next())
482 {
483 /* The storagegroup.dirname column uses utf8_bin collation, so Qt
484 * uses QString::fromAscii() for toString(). Explicitly convert the
485 * value using QString::fromUtf8() to prevent corruption. */
486 QString currentDir {QString::fromUtf8(query.value(1).toByteArray().constData())};
487 if (currentDir.endsWith("/"))
488 currentDir.remove(currentDir.length() - 1, 1);
489
490 if (!foundDirs.contains(currentDir))
491 {
492 if (QDir(currentDir).exists())
493 {
494 fsInfos.push_back(FileSystemInfo(localHostName, currentDir, query.value(0).toInt()));
495
496 foundDirs[currentDir] = true;
497 }
498 else
499 {
500 foundDirs[currentDir] = false;
501 }
502 }
503 }
504 }
505
506 return fsInfos;
507}
508
510{
512
513 {
514 QReadLocker rlock(&m_fsLock);
515 for (const auto* fs : std::as_const(m_fsMap))
516 {
517 disks << FileSystemInfoManager::GetInfoList(fs->GetSocket());
518 }
519 }
520
521 return disks;
522}
523
530 QStringList &slist)
531{
532 QStringList::const_iterator it = slist.cbegin() + 2;
533 RecordingInfo recinfo(it, slist.cend());
534
535 bool exists = false;
536
537 QString pburl;
538 if (recinfo.HasPathname())
539 {
540 pburl = GetPlaybackURL(&recinfo);
541 exists = QFileInfo::exists(pburl);
542 if (!exists)
543 pburl.clear();
544 }
545
546 QStringList res(QString::number(static_cast<int>(exists)));
547 res << pburl;
548 socket->WriteStringList(res);
549 return true;
550}
551
552
558 QStringList &slist)
559{
560 QString storageGroup = "Default";
561 QStringList res;
562
563 if (slist.size() == 3)
564 {
565 if (!slist[2].isEmpty())
566 storageGroup = slist[2];
567 }
568 else if (slist.size() != 2)
569 {
570 return false;
571 }
572
573 const QString& filename = slist[1];
574 if ((filename.isEmpty()) ||
575 (filename.contains("/../")) ||
576 (filename.startsWith("../")))
577 {
578 LOG(VB_GENERAL, LOG_ERR,
579 QString("ERROR checking for file, filename '%1' "
580 "fails sanity checks").arg(filename));
581 res << "";
582 socket->WriteStringList(res);
583 return true;
584 }
585
586 StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());
587 QString fullname = sgroup.FindFile(filename);
588
589 if (!fullname.isEmpty())
590 {
591 res << "1"
592 << fullname;
593
594 // TODO: convert me to QFile
595 struct stat fileinfo {};
596 if (stat(fullname.toLocal8Bit().constData(), &fileinfo) >= 0)
597 {
598 res << QString::number(fileinfo.st_dev)
599 << QString::number(fileinfo.st_ino)
600 << QString::number(fileinfo.st_mode)
601 << QString::number(fileinfo.st_nlink)
602 << QString::number(fileinfo.st_uid)
603 << QString::number(fileinfo.st_gid)
604 << QString::number(fileinfo.st_rdev)
605 << QString::number(fileinfo.st_size)
606#ifdef Q_OS_WINDOWS
607 << "0"
608 << "0"
609#else
610 << QString::number(fileinfo.st_blksize)
611 << QString::number(fileinfo.st_blocks)
612#endif
613 << QString::number(fileinfo.st_atime)
614 << QString::number(fileinfo.st_mtime)
615 << QString::number(fileinfo.st_ctime);
616 }
617 }
618 else
619 {
620 res << "0";
621 }
622
623 socket->WriteStringList(res);
624 return true;
625}
626
632 QStringList &slist)
633{
634 QString storageGroup = "Default";
635 QString hostname = gCoreContext->GetHostName();
636 QString filename = "";
637 QStringList res;
638
639 switch (slist.size()) {
640 case 4:
641 if (!slist[3].isEmpty())
642 hostname = slist[3];
643 [[fallthrough]];
644 case 3:
645 if (!slist[2].isEmpty())
646 storageGroup = slist[2];
647 [[fallthrough]];
648 case 2:
649 filename = slist[1];
650 if (filename.isEmpty() ||
651 filename.contains("/../") ||
652 filename.startsWith("../"))
653 {
654 LOG(VB_GENERAL, LOG_ERR,
655 QString("ERROR checking for file, filename '%1' "
656 "fails sanity checks").arg(filename));
657 res << "";
658 socket->WriteStringList(res);
659 return true;
660 }
661 break;
662 default:
663 return false;
664 }
665
666 QString hash = "";
667
669 {
670 // looking for file on me, return directly
671 StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());
672 QString fullname = sgroup.FindFile(filename);
673 hash = FileHash(fullname);
674 }
675 else
676 {
677 QReadLocker rlock(&m_fsLock);
678 if (m_fsMap.contains(hostname))
679 {
680 // looking for file on connected host, query from it
681 if (m_fsMap.value(hostname)->SendReceiveStringList(slist))
682 hash = slist[0];
683 }
684 // I deleted the incorrect SQL select that was supposed to get
685 // host name from ip address. Since it cannot work and has
686 // been there 6 years I assume it is not important.
687 }
688
689
690 res << hash;
691 socket->WriteStringList(res);
692
693 return true;
694}
695
697 QStringList &slist)
698{
699 if (slist.size() != 3)
700 return false;
701
702 return HandleDeleteFile(socket, slist[1], slist[2]);
703}
704
705bool FileServerHandler::DeleteFile(const QString& filename, const QString& storagegroup)
706{
707 return HandleDeleteFile(nullptr, filename, storagegroup);
708}
709
711 const QString& filename, const QString& storagegroup)
712{
713 StorageGroup sgroup(storagegroup, "", false);
714 QStringList res;
715
716 if ((filename.isEmpty()) ||
717 (filename.contains("/../")) ||
718 (filename.startsWith("../")))
719 {
720 LOG(VB_GENERAL, LOG_ERR,
721 QString("ERROR deleting file, filename '%1' fails sanity checks")
722 .arg(filename));
723 if (socket)
724 {
725 res << "0";
726 socket->WriteStringList(res);
727 return true;
728 }
729 return false;
730 }
731
732 QString fullfile = sgroup.FindFile(filename);
733
734 if (fullfile.isEmpty())
735 {
736 LOG(VB_GENERAL, LOG_ERR,
737 QString("Unable to find %1 in HandleDeleteFile()") .arg(filename));
738 if (socket)
739 {
740 res << "0";
741 socket->WriteStringList(res);
742 return true;
743 }
744 return false;
745 }
746
747 QFile checkFile(fullfile);
748 if (checkFile.exists())
749 {
750 if (socket)
751 {
752 res << "1";
753 socket->WriteStringList(res);
754 }
756 deletethread->AddFile(fullfile);
757 }
758 else
759 {
760 LOG(VB_GENERAL, LOG_ERR, QString("Error deleting file: '%1'")
761 .arg(fullfile));
762 if (socket)
763 {
764 res << "0";
765 socket->WriteStringList(res);
766 }
767 }
768
769 return true;
770}
771
773{
775 return deletethread->AddFile(handler);
776}
777
779 QStringList &slist)
780{
781 QStringList res;
782
783 bool fileNamesOnly = false;
784 if (slist.size() == 5)
785 {
786 fileNamesOnly = (slist[4].toInt() != 0);
787 }
788 else if (slist.size() != 4)
789 {
790 LOG(VB_GENERAL, LOG_ERR, QString("Invalid Request. %1")
791 .arg(slist.join("[]:[]")));
792 res << "EMPTY LIST";
793 socket->WriteStringList(res);
794 return true;
795 }
796
797 QString host = gCoreContext->GetHostName();
798 QString wantHost = slist[1];
799 QString groupname = slist[2];
800 QString path = slist[3];
801
802 LOG(VB_FILE, LOG_INFO,
803 QString("HandleSGGetFileList: group = %1 host = %2 "
804 "path = %3 wanthost = %4")
805 .arg(groupname, host, path, wantHost));
806
807 if (gCoreContext->IsThisHost(wantHost))
808 {
809 StorageGroup sg(groupname, host);
810 LOG(VB_FILE, LOG_INFO, "Getting local info");
811 if (fileNamesOnly)
812 res = sg.GetFileList(path);
813 else
814 res = sg.GetFileInfoList(path);
815
816 if (res.count() == 0)
817 res << "EMPTY LIST";
818 }
819 else
820 {
821 // handle request on remote server
822 SocketHandler *remsock = nullptr;
823 {
824 QReadLocker rlock(&m_fsLock);
825 if (m_fsMap.contains(wantHost))
826 {
827 remsock = m_fsMap.value(wantHost);
828 remsock->IncrRef();
829 }
830 }
831
832 if (remsock)
833 {
834 LOG(VB_FILE, LOG_INFO, "Getting remote info");
835 res << "QUERY_SG_GETFILELIST" << wantHost << groupname << path
836 << QString::number(static_cast<int>(fileNamesOnly));
837 remsock->SendReceiveStringList(res);
838 remsock->DecrRef();
839 }
840 else
841 {
842 LOG(VB_FILE, LOG_ERR, QString("Failed to grab slave socket : %1 :")
843 .arg(wantHost));
844 res << "SLAVE UNREACHABLE: " << wantHost;
845 }
846 }
847
848 socket->WriteStringList(res);
849 return true;
850}
851
853 QStringList &slist)
854{
855 QStringList res;
856
857 if (slist.size() != 4)
858 {
859 LOG(VB_GENERAL, LOG_ERR, QString("Invalid Request. %1")
860 .arg(slist.join("[]:[]")));
861 res << "EMPTY LIST";
862 socket->WriteStringList(res);
863 return true;
864 }
865
866 QString wantHost = slist[1];
867 QString groupname = slist[2];
868 QString filename = slist[3];
869
870 LOG(VB_FILE, LOG_DEBUG, QString("HandleSGFileQuery: myth://%1@%2/%3")
871 .arg(groupname, wantHost, filename));
872
873 if (gCoreContext->IsThisHost(wantHost))
874 {
875 // handle request locally
876 LOG(VB_FILE, LOG_DEBUG, QString("Getting local info"));
877 StorageGroup sg(groupname, gCoreContext->GetHostName());
878 res = sg.GetFileInfo(filename);
879
880 if (res.count() == 0)
881 res << "EMPTY LIST";
882 }
883 else
884 {
885 // handle request on remote server
886 SocketHandler *remsock = nullptr;
887 {
888 QReadLocker rlock(&m_fsLock);
889 if (m_fsMap.contains(wantHost))
890 {
891 remsock = m_fsMap.value(wantHost);
892 remsock->IncrRef();
893 }
894 }
895
896 if (remsock)
897 {
898 res << "QUERY_SG_FILEQUERY" << wantHost << groupname << filename;
899 remsock->SendReceiveStringList(res);
900 remsock->DecrRef();
901 }
902 else
903 {
904 res << "SLAVE UNREACHABLE: " << wantHost;
905 }
906 }
907
908 socket->WriteStringList(res);
909 return true;
910}
911
913 QStringList &commands, QStringList &slist)
914{
915 if (commands.size() != 2)
916 return false;
917
918 if (slist.size() < 2)
919 return false;
920
921 QStringList res;
922 int recnum = commands[1].toInt();
923 FileTransfer *ft = nullptr;
924
925 {
926 QReadLocker rlock(&m_ftLock);
927 if (!m_ftMap.contains(recnum))
928 {
929 if (slist[1] == "DONE")
930 {
931 res << "OK";
932 }
933 else
934 {
935 LOG(VB_GENERAL, LOG_ERR,
936 QString("Unknown file transfer socket: %1").arg(recnum));
937 res << "ERROR"
938 << "unknown_file_transfer_socket";
939 }
940
941 socket->WriteStringList(res);
942 return true;
943 }
944
945 ft = m_ftMap.value(recnum);
946 ft->IncrRef();
947 }
948
949 if (slist[1] == "REQUEST_BLOCK")
950 {
951 if (slist.size() != 3)
952 {
953 LOG(VB_GENERAL, LOG_ERR, "Invalid QUERY_FILETRANSFER "
954 "REQUEST_BLOCK call");
955 res << "ERROR" << "invalid_call";
956 }
957 else
958 {
959 int size = slist[2].toInt();
960 res << QString::number(ft->RequestBlock(size));
961 }
962 }
963 else if (slist[1] == "WRITE_BLOCK")
964 {
965 if (slist.size() != 3)
966 {
967 LOG(VB_GENERAL, LOG_ERR, "Invalid QUERY_FILETRANSFER "
968 "WRITE_BLOCK call");
969 res << "ERROR" << "invalid_call";
970 }
971 else
972 {
973 int size = slist[2].toInt();
974 res << QString::number(ft->WriteBlock(size));
975 }
976 }
977 else if (slist[1] == "SEEK")
978 {
979 if (slist.size() != 5)
980 {
981 LOG(VB_GENERAL, LOG_ERR, "Invalid QUERY_FILETRANSFER SEEK call");
982 res << "ERROR" << "invalid_call";
983 }
984 else
985 {
986 long long pos = slist[2].toLongLong();
987 int whence = slist[3].toInt();
988 long long curpos = slist[4].toLongLong();
989
990 res << QString::number(ft->Seek(curpos, pos, whence));
991 }
992 }
993 else if (slist[1] == "IS_OPEN")
994 {
995 res << QString::number(static_cast<int>(ft->isOpen()));
996 }
997 else if (slist[1] == "DONE")
998 {
999 ft->Stop();
1000 res << "OK";
1001 }
1002 else if (slist[1] == "SET_TIMEOUT")
1003 {
1004 if (slist.size() != 3)
1005 {
1006 LOG(VB_GENERAL, LOG_ERR, "Invalid QUERY_FILETRANSFER "
1007 "SET_TIMEOUT call");
1008 res << "ERROR" << "invalid_call";
1009 }
1010 else
1011 {
1012 bool fast = slist[2].toInt() != 0;
1013 ft->SetTimeout(fast);
1014 res << "OK";
1015 }
1016 }
1017 else if (slist[1] == "REQUEST_SIZE")
1018 {
1019 // return size and if the file is not opened for writing
1020 res << QString::number(ft->GetFileSize());
1021 res << QString::number(static_cast<int>(!gCoreContext->IsRegisteredFileForWrite(ft->GetFileName())));
1022 }
1023 else
1024 {
1025 LOG(VB_GENERAL, LOG_ERR, "Invalid QUERY_FILETRANSFER call");
1026 res << "ERROR" << "invalid_call";
1027 }
1028
1029 ft->DecrRef();
1030 socket->WriteStringList(res);
1031 return true;
1032}
1033
1035 QStringList &slist)
1036{
1037 QStringList res;
1038
1039 if (slist.size() != 4)
1040 {
1041 res << "ERROR" << QString("Bad %1 command").arg(slist[0]);
1042 socket->WriteStringList(res);
1043 return true;
1044 }
1045
1046 bool synchronous = (slist[0] == "DOWNLOAD_FILE_NOW");
1047 QString srcURL = slist[1];
1048 QString storageGroup = slist[2];
1049 QString filename = slist[3];
1050 StorageGroup sgroup(storageGroup, gCoreContext->GetHostName(), false);
1051 QString outDir = sgroup.FindNextDirMostFree();
1052 QString outFile;
1053
1054 if (filename.isEmpty())
1055 {
1056 QFileInfo finfo(srcURL);
1057 filename = finfo.fileName();
1058 }
1059
1060 if (outDir.isEmpty())
1061 {
1062 LOG(VB_GENERAL, LOG_ERR, QString("Unable to determine directory "
1063 "to write to in %1 write command").arg(slist[0]));
1064 res << "ERROR" << "downloadfile_directory_not_found";
1065 socket->WriteStringList(res);
1066 return true;
1067 }
1068
1069 if ((filename.contains("/../")) ||
1070 (filename.startsWith("../")))
1071 {
1072 LOG(VB_GENERAL, LOG_ERR, QString("ERROR: %1 write "
1073 "filename '%2' does not pass sanity checks.")
1074 .arg(slist[0], filename));
1075 res << "ERROR" << "downloadfile_filename_dangerous";
1076 socket->WriteStringList(res);
1077 return true;
1078 }
1079
1080 outFile = outDir + "/" + filename;
1081
1082 if (synchronous)
1083 {
1084 if (GetMythDownloadManager()->download(srcURL, outFile))
1085 {
1086 res << "OK"
1087 << gCoreContext->GetMasterHostPrefix(storageGroup)
1088 + filename;
1089 }
1090 else
1091 {
1092 res << "ERROR";
1093 }
1094 }
1095 else
1096 {
1097 QMutexLocker locker(&m_downloadURLsLock);
1098 m_downloadURLs[outFile] =
1099 gCoreContext->GetMasterHostPrefix(storageGroup) +
1101
1102 GetMythDownloadManager()->queueDownload(srcURL, outFile, this);
1103 res << "OK"
1104 << gCoreContext->GetMasterHostPrefix(storageGroup) + filename;
1105 }
1106
1107 socket->WriteStringList(res);
1108 return true;
1109}
1110
1111#include "moc_fileserverhandler.cpp"
void start(void)
Definition: mainserver.h:86
bool AddFile(const QString &path)
static bool HandleQueryCheckFile(SocketHandler *socket, QStringList &slist)
bool HandleFileQuery(SocketHandler *socket, QStringList &slist)
bool HandleQueryFileTransfer(SocketHandler *socket, QStringList &commands, QStringList &slist)
bool HandleQuery(SocketHandler *socket, QStringList &commands, QStringList &slist) override
static void RunDeleteThread(void)
FileSystemInfoList QueryAllFileSystems(void)
QMap< QString, QString > m_downloadURLs
static FileSystemInfoList QueryFileSystems(void)
bool HandleDownloadFile(SocketHandler *socket, QStringList &slist)
bool HandleQueryFreeSpaceSummary(SocketHandler *socket)
bool HandleQueryFileHash(SocketHandler *socket, QStringList &slist)
static bool HandleQueryFileExists(SocketHandler *socket, QStringList &slist)
bool HandleAnnounce(MythSocket *socket, QStringList &commands, QStringList &slist) override
QReadWriteLock m_ftLock
QReadWriteLock m_fsLock
bool HandleGetFileList(SocketHandler *socket, QStringList &slist)
bool HandleQueryFreeSpaceList(SocketHandler *socket)
static bool HandleQueryFreeSpace(SocketHandler *socket)
void connectionClosed(MythSocket *socket) override
static bool DeleteFile(const QString &filename, const QString &storagegroup)
QMap< int, FileTransfer * > m_ftMap
void connectionAnnounced(MythSocket *socket, QStringList &commands, QStringList &slist) override
QMap< QString, SocketHandler * > m_fsMap
static QString LocalFilePath(const QString &path, const QString &wantgroup)
static bool HandleDeleteFile(SocketHandler *socket, QStringList &slist)
int WriteBlock(int size)
void Stop(void)
long long Seek(long long curpos, long long pos, int whence)
QString GetFileName(void)
bool isOpen(void)
uint64_t GetFileSize(void)
void SetTimeout(bool fast)
int RequestBlock(int size)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:129
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:839
QVariant value(int i) const
Definition: mythdbcon.h:205
int size(void) const
Definition: mythdbcon.h:215
bool isActive(void) const
Definition: mythdbcon.h:216
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:620
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:890
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:814
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:552
bool isRunning(void) const
Definition: mthread.cpp:247
QString GetHostName(void)
bool IsThisHost(const QString &addr)
is this address mapped to this host
QString GetMasterHostPrefix(const QString &storageGroup=QString(), const QString &path=QString())
bool IsRegisteredFileForWrite(const QString &file)
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
void queueDownload(const QString &url, const QString &dest, QObject *caller, bool reload=false)
Adds a url to the download queue.
SocketHandler * GetConnectionBySocket(MythSocket *socket)
void AddSocketHandler(SocketHandler *socket)
Class for communcating between myth backends and frontends.
Definition: mythsocket.h:26
int GetSocketDescriptor(void) const
Definition: mythsocket.cpp:580
bool WriteStringList(const QStringList &list)
Definition: mythsocket.cpp:306
Holds information on recordings and videos.
Definition: programinfo.h:74
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:380
bool HasPathname(void) const
Definition: programinfo.h:365
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:36
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
virtual int IncrRef(void)
Increments reference count.
void BlockShutdown(bool block)
Definition: sockethandler.h:35
bool WriteStringList(const QStringList &strlist)
bool SendReceiveStringList(QStringList &strlist, uint min_reply_length=0)
MythSocketManager * m_parent
void Init(const QString &group="Default", const QString &hostname="", bool allowFallback=true)
Initilizes the groupname, hostname, and dirlist.
QStringList GetFileInfo(const QString &filename)
QString FindFile(const QString &filename)
static const QStringList kSpecialGroups
Definition: storagegroup.h:46
QStringList GetFileInfoList(const QString &Path)
QString FindNextDirMostFree(void)
QStringList GetFileList(const QString &Path, bool recursive=false)
static QString GetRelativePathname(const QString &filename)
Returns the relative pathname of a file by comparing the filename against all Storage Group directori...
DeleteThread * deletethread
QString GetPlaybackURL(ProgramInfo *pginfo, bool storePath)
QVector< FileSystemInfo > FileSystemInfoList
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
static constexpr qint64 kReadTestSize
QString FileHash(const QString &filename)
MBASE_PUBLIC FileSystemInfoList GetInfoList(MythSocket *sock=nullptr)
MBASE_PUBLIC QStringList ToStringList(const FileSystemInfoList &fsInfos)
MBASE_PUBLIC void Consolidate(FileSystemInfoList &disks, bool merge, int64_t fuzz, const QString &total_name={})
string hostname
Definition: caa.py:17
bool exists(str path)
Definition: xbmcvfs.py:51