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