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