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