Index: libs/libmythtv/previewgenerator.cpp =================================================================== --- libs/libmythtv/previewgenerator.cpp (revision 9611) +++ libs/libmythtv/previewgenerator.cpp (working copy) @@ -62,9 +62,23 @@ // Try to find a local means to access file... QString baseName = programInfo.GetRecordBasename(); QString prefix = gContext->GetSetting("RecordFilePrefix"); - QString localFN = QString("%1/%2").arg(prefix).arg(baseName); - if (!QFileInfo(localFN).exists()) + QString localFN; + + // search through all recording directories + bool found = false; + QStringList recording_prefixes = QStringList::split(",",gContext->GetSetting("RecordFilePrefix")); + for (unsigned int index = 0; index < recording_prefixes.size(); ++index) { + if (!found) + { + localFN = recording_prefixes[index] + "/" + baseName; + if (QFileInfo(localFN).exists()) + found = true; + } + } + + if (!found) + { // GetPlaybackURL tries to return a local filename if one exists localFN = programInfo.GetPlaybackURL(); if (!(localFN.left(1) == "/" && QFileInfo(localFN).exists())) Index: libs/libmythtv/RingBuffer.cpp =================================================================== --- libs/libmythtv/RingBuffer.cpp (revision 9611) +++ libs/libmythtv/RingBuffer.cpp (working copy) @@ -162,23 +162,33 @@ bool is_local = false; bool is_dvd = false; + bool found = false; (void) is_dvd; // not used when frontend is disabled. if ((filename.left(7) == "myth://") && (filename.length() > 7 )) { - QString local_pathname = gContext->GetSetting("RecordFilePrefix"); + QString local_pathname; int hostlen = filename.find(QRegExp("/"), 7); if (hostlen != -1) { - local_pathname += filename.right(filename.length() - hostlen); + // loop through our recording directories looking for it + QStringList recording_prefixes = QStringList::split(",", gContext->GetSetting("RecordFilePrefix")); + for (unsigned int index = 0; index < recording_prefixes.size(); ++index) + { + if (!found) + { + local_pathname = recording_prefixes[index] + filename.right(filename.length() - hostlen); - QFile checkFile(local_pathname); - if (checkFile.exists()) - { - is_local = true; - filename = local_pathname; + QFile checkFile(local_pathname); + if (checkFile.exists()) + { + found = true; + is_local = true; + filename = local_pathname; + } + } } } } Index: libs/libmythtv/tv_rec.cpp =================================================================== --- libs/libmythtv/tv_rec.cpp (revision 9611) +++ libs/libmythtv/tv_rec.cpp (working copy) @@ -627,8 +627,29 @@ if (!curRec) return; - curRec->StartedRecording(rbFilePrefix, rbFileExt); - VERBOSE(VB_RECORD, LOC + "StartedRecording("< highest_free_space) + { + highest_free_space = cur_free_space; + chosen_index = index; + } + VERBOSE(VB_RECORD, LOC + QString("filesystem: (%1)%2, free: %3, total: %4, " + "used: %5 (chosen %6)") + .arg(index).arg(recording_prefixes[index]).arg(cur_free_space) + .arg(total).arg(used).arg(chosen_index)); + } + + curRec->StartedRecording(recording_prefixes[chosen_index], rbFileExt); + VERBOSE(VB_RECORD, LOC + "StartedRecording LTD("<GetFileName()<<")"); if (curRec->chancommfree != 0) Index: libs/libmythtv/programinfo.cpp =================================================================== --- libs/libmythtv/programinfo.cpp (revision 9611) +++ libs/libmythtv/programinfo.cpp (working copy) @@ -1294,7 +1294,9 @@ { QString starts = recstartts.toString("yyyyMMddhhmmss"); - QString retval = QString("%1_%2.%3").arg(chanid) + QString retval = QString("%1_%2_%3_%4_%5.%6") + .arg(title).arg(chanstr) + .arg(chansign).arg(chanid) .arg(starts).arg(ext); return retval; @@ -1362,6 +1364,23 @@ */ QString ProgramInfo::GetRecordFilename(const QString &prefix) const { + // since we support multiple recording directories, try to + // find out which one we used for storing this file in .. + // if we cannot find it then just fall back to existing + // behavior + + QStringList recording_prefixes = QStringList::split(",",prefix); + QString filename; + QString basename = GetRecordBasename(); + for (unsigned int index = 0; index < recording_prefixes.size(); ++index) + { + filename = QString("%1/%2").arg(recording_prefixes[index]).arg(basename); + QFile checkFile(filename); + if (checkFile.exists()) + return filename; + } + + // bah - just return what we would have done previously return QString("%1/%2").arg(prefix).arg(GetRecordBasename()); } Index: programs/mythbackend/mainserver.cpp =================================================================== --- programs/mythbackend/mainserver.cpp (revision 9611) +++ programs/mythbackend/mainserver.cpp (working copy) @@ -1112,16 +1112,30 @@ proginfo->stars = query.value(31).toDouble(); - QString lpath = fileprefix + "/" + basename; + // since we support multiple recording directories, + // be sure to specify actual directory where file is + // stored. if we cannot find it, fallback + QString lpath; + QStringList recording_prefixes = QStringList::split(",",fileprefix); + int found_index = -1; + for (int index = 0; index < (int)recording_prefixes.size() && found_index == -1; ++index) + { + lpath = recording_prefixes[index] + "/" + basename; + QFile checkFile(lpath); + if (checkFile.exists()) + found_index = index; + } + if (found_index == -1) + lpath = fileprefix + "/" + basename; + PlaybackSock *slave = NULL; - QFile checkFile(lpath); if (proginfo->hostname != gContext->GetHostName()) slave = getSlaveByHostname(proginfo->hostname); - if ((masterBackendOverride && checkFile.exists()) || + if ((masterBackendOverride && (found_index != -1)) || (proginfo->hostname == gContext->GetHostName()) || - (!slave && checkFile.exists())) + (!slave && (found_index != -1))) { if (islocal) proginfo->pathname = lpath; @@ -1785,7 +1799,16 @@ { QStringList strlist; long long totalKB = -1, usedKB = -1; - getDiskSpace(recordfileprefix, totalKB, usedKB); + long long cur_totalKB = -1, cur_usedKB = -1, cur_availKB = -1; + + QStringList recording_prefixes = QStringList::split(",", recordfileprefix); + for (unsigned int index = 0; index < recording_prefixes.size(); ++index) + { + cur_availKB = getDiskSpace(recording_prefixes[index], cur_totalKB, cur_usedKB); + totalKB += cur_totalKB; + usedKB += cur_usedKB; + } + if (!allHosts) { encodeLongLong(strlist, totalKB); @@ -3585,8 +3608,9 @@ } else { + QStringList recording_prefixes = QStringList::split(",", gContext->GetFilePrefix()); lpath = lpath.section('/', -1); - lpath = gContext->GetFilePrefix() + "/" + lpath; + lpath = recording_prefixes[0] + "/" + lpath; } VERBOSE(VB_FILE, QString("Local file path: %1").arg(lpath)); @@ -3913,8 +3937,16 @@ // drive space --------------------- long long iTotal = -1, iUsed = -1, iAvail = -1; + long long cur_iTotal = -1, cur_iUsed = -1, cur_iAvail = -1; - iAvail = getDiskSpace(recordfileprefix, iTotal, iUsed); + QStringList recording_prefixes = QStringList::split(",", recordfileprefix); + for (unsigned int index = 0; index < recording_prefixes.size(); ++index) + { + cur_iAvail = getDiskSpace(recording_prefixes[index], cur_iTotal, cur_iUsed); + iAvail += cur_iAvail; + iTotal += cur_iTotal; + iUsed += cur_iUsed; + } storage.setAttribute("_local_:total", (int)(iTotal>>10)); storage.setAttribute("_local_:used" , (int)(iUsed>>10)); Index: programs/mythbackend/main.cpp =================================================================== --- programs/mythbackend/main.cpp (revision 9611) +++ programs/mythbackend/main.cpp (working copy) @@ -39,7 +39,6 @@ Scheduler *sched = NULL; JobQueue *jobqueue = NULL; QString pidfile; -QString lockfile_location; HouseKeeper *housekeeping = NULL; QString logfile = ""; @@ -201,7 +200,12 @@ if (pidfile != "") unlink(pidfile.ascii()); - unlink(lockfile_location.ascii()); + QStringList recording_prefixes = QStringList::split(",", gContext->GetSetting("RecordFilePrefix")); + for (unsigned int index = 0; index < recording_prefixes.size(); ++index) + { + QString lockfile_location = recording_prefixes[index] + "/nfslockfile.lock"; + unlink(lockfile_location.ascii()); + } signal(SIGHUP, SIG_DFL); } @@ -600,8 +604,6 @@ VERBOSE(VB_IMPORTANT, QString("Enabled verbose msgs: %1").arg(verboseString)); - lockfile_location = gContext->GetSetting("RecordFilePrefix") + "/nfslockfile.lock"; - if (ismaster) // Create a file in the recording directory. A slave encoder will // look for this file and return 0 bytes free if it finds it when it's @@ -610,14 +612,19 @@ // If the slave doesn't find this file then it will assume that it has // a seperate store. { - if (creat(lockfile_location.ascii(), 0664) == -1) + QStringList recording_prefixes = QStringList::split(",", gContext->GetSetting("RecordFilePrefix")); + for (unsigned int index = 0; index < recording_prefixes.size(); ++index) { - perror(lockfile_location.ascii()); - cerr << "Unable to open lockfile!\n" - << "Be sure that \'" << gContext->GetSetting("RecordFilePrefix") - << "\' exists and that both \nthe directory and that " - << "file are writeable by this user.\n"; - return BACKEND_EXIT_OPENING_VLOCKFILE_ERROR; + QString lockfile_location = recording_prefixes[index] + "/nfslockfile.lock"; + if (creat(lockfile_location.ascii(), 0664) == -1) + { + perror(lockfile_location.ascii()); + cerr << "Unable to open lockfile!\n" + << "Be sure that \'" << gContext->GetSetting("RecordFilePrefix") + << "\' exists and that both \nthe directory and that " + << "file are writeable by this user.\n"; + return BACKEND_EXIT_OPENING_VLOCKFILE_ERROR; + } } } Index: programs/mythbackend/autoexpire.cpp =================================================================== --- programs/mythbackend/autoexpire.cpp (revision 9611) +++ programs/mythbackend/autoexpire.cpp (working copy) @@ -114,10 +114,32 @@ sharing = true; else if (enc->IsConnected()) { + + // to support multiple recording directories, we need to base + // the expiry interval based on the SMALLEST filesystem we have + // to accomplish this, iterate over all our disks & use only + // the one with the least free space long long total, used; - beginSize = getDiskSpace(recordFilePrefix, total, used); + + int chosen_index = -1; + long long cur_free_space = -1, lowest_free_space = -1; + QStringList recording_prefixes = QStringList::split(",",recordFilePrefix); + for (unsigned int index = 0; index < recording_prefixes.size(); ++index) + { + cur_free_space = getDiskSpace(recording_prefixes[index], total, used); + if ((cur_free_space < lowest_free_space) || (chosen_index == -1)) + { + lowest_free_space = cur_free_space; + chosen_index = index; + } + DBG_CALC_PARAM(QString("filesystem: (%1)%2, free: %3, total: %4, used: %5 (chosen %6)") + .arg(index).arg(recording_prefixes[index]).arg(cur_free_space) + .arg(total).arg(used).arg(chosen_index)); + } + + beginSize = getDiskSpace(recording_prefixes[chosen_index], total, used); remoteSize = enc->GetFreeDiskSpace(); - endSize = getDiskSpace(recordFilePrefix, total, used); + endSize = getDiskSpace(recording_prefixes[chosen_index], total, used); DBG_CALC_PARAM("CalcParams() -- A 2"); if (beginSize<0 || remoteSize<0 || endSize<0) @@ -219,7 +241,7 @@ * * Every "AutoExpireFrequency" minutes this will delete as many * files as needed to free "AutoExpireDiskThreshold" gigabytes of - * space on the filesystem containing "RecordFilePrefix". + * space on the filesystems containing "RecordFilePrefix". * * If this is run on the master backend this will also delete * programs exceeding the maximum number of episodes of that @@ -289,10 +311,12 @@ long long availFreeKB = 0; long long tKB, uKB; - if ((availFreeKB = getDiskSpace(record_file_prefix, tKB, uKB)) < 0) + QStringList recording_prefixes = QStringList::split(",",record_file_prefix); + // only ever store live tv on first recording directory .. + if ((availFreeKB = getDiskSpace(recording_prefixes[0], tKB, uKB)) < 0) { QString msg = QString("ERROR: Could not calculate free space for %1.") - .arg(record_file_prefix); + .arg(recording_prefixes[0]); VERBOSE(VB_IMPORTANT, LOC + msg); gContext->LogEntry("mythbackend", LP_WARNING, "Autoexpire Recording", msg); @@ -302,7 +326,7 @@ VERBOSE(VB_FILE, LOC + QString("ExpireLiveTV(%1)").arg(type)); ClearExpireList(); FillDBOrdered(type); - SendDeleteMessages(availFreeKB, 0, true); + SendDeleteMessages(availFreeKB, 0, recording_prefixes[0], true); } } @@ -315,39 +339,47 @@ long long availFreeKB = 0; long long tKB, uKB; - if ((availFreeKB = getDiskSpace(record_file_prefix, tKB, uKB)) < 0) + // loop through all our recording directories, checking for minimum + // space on each. trigger deletions if below that threshold.. + QStringList recording_prefixes = QStringList::split(",",record_file_prefix); + for (unsigned int index = 0; index < recording_prefixes.size(); ++index) { - QString msg = QString("ERROR: Could not calculate free space."); - VERBOSE(VB_IMPORTANT, LOC + msg); - gContext->LogEntry("mythbackend", LP_WARNING, - "Autoexpire Recording", msg); - } - else if (((size_t)availFreeKB) < desired_space) - { - VERBOSE(VB_FILE, LOC + - QString("Running autoexpire, we want %1 MB free, " - "but only have %2 MB.") - .arg(desired_space/1024).arg(availFreeKB/1024)); + if ((availFreeKB = getDiskSpace(recording_prefixes[index], tKB, uKB)) < 0) + { + QString msg = QString("ERROR: Could not calculate free space for %1.") + .arg(recording_prefixes[index]); + VERBOSE(VB_IMPORTANT, LOC + msg); + gContext->LogEntry("mythbackend", LP_WARNING, + "Autoexpire Recording", msg); + } + else if (((size_t)availFreeKB) < desired_space) + { + VERBOSE(VB_FILE, LOC + + QString("Running autoexpire in %1, we want %2 MB free, " + "but only have %3 MB.") + .arg(recording_prefixes[index]) + .arg(desired_space/1024).arg(availFreeKB/1024)); - FillExpireList(); - SendDeleteMessages(availFreeKB, desired_space); + FillExpireList(); + SendDeleteMessages(availFreeKB, desired_space, recording_prefixes[index]); + } } } -/** \fn CheckFile(const ProgramInfo*, const QString&, const fsid_t&) +/** \fn CheckFile(const ProgramInfo*, QString&, const fsid_t&) * \brief Returns true iff file can be deleted. * * CheckFile makes sure the file exists and is stored on the same file * system as the recordfileprefix. * \param pginfo ProgramInfo for the program we wish to delete. - * \param recordfileprefix Path where new recordings are stored. + * \param dir Path where we are looking for files to remove from. * \param fsid Unique ID of recordfileprefix's filesystem. */ static bool CheckFile(const ProgramInfo *pginfo, - const QString &recordfileprefix, + QString &dir, const fsid_t& fsid) { - QString filename = pginfo->GetRecordFilename(recordfileprefix); + QString filename = pginfo->GetRecordFilename(dir); QFileInfo checkFile(filename); if (!checkFile.exists()) { @@ -361,7 +393,8 @@ gContext->LogEntry("mythbackend", LP_WARNING, "Autoexpire Recording", msg); } - else if (pginfo->hostname == gContext->GetHostName()) + else if ((pginfo->hostname == gContext->GetHostName()) && + (dir == gContext->GetSetting("RecordFilePrefix"))) { QString msg = QString("ERROR when trying to autoexpire file: %1. " @@ -386,36 +419,46 @@ return true; } -/** \fn AutoExpire::SendDeleteMessages(size_t, size_t, bool) +/** \fn AutoExpire::SendDeleteMessages(size_t, size_t, QString&, bool) * \brief This sends delete message to main event thread. */ void AutoExpire::SendDeleteMessages(size_t availFreeKB, size_t desiredFreeKB, - bool deleteAll) + QString &dir, bool deleteAll) { QString msg; - fsid_t fsid = UniqueFileSystemID(record_file_prefix); + fsid_t fsid = UniqueFileSystemID(dir); if (expire_list.size() == 0) { if ((!deleteAll) && (desiredFreeKB > 0) && (availFreeKB < desiredFreeKB)) - VERBOSE(VB_FILE, LOC + "SendDeleteMessages. WARNING: below free " - "disk space threshold, but nothing to expire~"); + { + QString msg = + QString("SendDeleteMessages. WARNING: below free " + "disk space threshold, but nothing to expire~ " + "(%1, %2 available, %3 desired)") + .arg(dir).arg(availFreeKB).arg(desiredFreeKB); + VERBOSE(VB_FILE, LOC + msg); + } else - VERBOSE(VB_FILE, LOC + "SendDeleteMessages. Nothing to expire."); - + { + QString msg = + QString("SendDeleteMessages. Nothing to expire. (%1)").arg(dir); + VERBOSE(VB_FILE, LOC + msg); + } return; } - VERBOSE(VB_FILE, LOC + "SendDeleteMessages, cycling through expire list."); + VERBOSE(VB_FILE, LOC + QString("SendDeleteMessages, cycling through " + "expire list (%1).").arg(dir)); pginfolist_t::iterator it = expire_list.begin(); while (it != expire_list.end() && (deleteAll || availFreeKB < desiredFreeKB)) { VERBOSE(VB_FILE, QString(" Checking %1 @ %2") .arg((*it)->chanid).arg((*it)->startts.toString())); - if (CheckFile(*it, record_file_prefix, fsid)) + if (CheckFile(*it, dir, fsid)) { // Print informative message msg = QString("Expiring \"%1\" from %2, %3 MBytes") @@ -430,9 +473,10 @@ } else msg += QString(", free space is too low (have %1 MBytes free " - ", but want %2 MBytes)") + ", but want %2 MBytes on %3)") .arg(availFreeKB/1024) - .arg(desiredFreeKB/1024); + .arg(desiredFreeKB/1024) + .arg(dir); if (print_verbose_messages & VB_IMPORTANT) VERBOSE(VB_IMPORTANT, msg); @@ -465,7 +509,7 @@ if (!deleteAll && availFreeKB < desiredFreeKB) { msg = QString("ERROR when trying to autoexpire files. " - "No recordings available to expire."); + "No recordings available to expire on %1.").arg(dir); VERBOSE(VB_IMPORTANT, LOC + msg); gContext->LogEntry("mythbackend", LP_WARNING, "Autoexpire Recording", msg); Index: programs/mythbackend/autoexpire.h =================================================================== --- programs/mythbackend/autoexpire.h (revision 9611) +++ programs/mythbackend/autoexpire.h (working copy) @@ -49,7 +49,7 @@ void FillDBOrdered(int expMethod, bool allHosts = false); void SendDeleteMessages(size_t availFreeKB, size_t desiredFreeKB, - bool deleteAll = false); + QString &dir, bool deleteAll = false); void ClearExpireList(void); void Sleep(int sleepTime); Index: programs/mythbackend/encoderlink.cpp =================================================================== --- programs/mythbackend/encoderlink.cpp (revision 9611) +++ programs/mythbackend/encoderlink.cpp (working copy) @@ -243,7 +243,21 @@ if (!use_cache) { if (local) - freeDiskSpaceKB = getDiskSpace(recordfileprefix, totalKB, usedKB); + { + // since we can record to multiple mount points and always choose + // the directory with the most free space, return just the space + // for that one mount point + long long cur_free_space; + freeDiskSpaceKB = -1; + QStringList recording_prefixes = QStringList::split(",",recordfileprefix); + for (unsigned int index = 0; index < recording_prefixes.size(); ++index) + { + cur_free_space = getDiskSpace(recording_prefixes[index], totalKB, usedKB); + + if (cur_free_space > freeDiskSpaceKB) + freeDiskSpaceKB = cur_free_space; + } + } else if (sock) { sock->GetFreeDiskSpace(totalKB, usedKB); Index: setup/checksetup.cpp =================================================================== --- setup/checksetup.cpp (revision 9611) +++ setup/checksetup.cpp (working copy) @@ -60,8 +60,13 @@ if (query.size()) { query.next(); - if (checkPath(query.value(0).toString(), probs)) - problemFound = true; + + QStringList recording_prefixes = QStringList::split(",", query.value(0).toString()); + for (unsigned int index = 0; index < recording_prefixes.size(); ++index) + { + if (checkPath(recording_prefixes[index], probs)) + problemFound = true; + } } else VERBOSE(VB_GENERAL, QString("RecordFilePrefix is not set?")); Index: setup/backendsettings.cpp =================================================================== --- setup/backendsettings.cpp (revision 9611) +++ setup/backendsettings.cpp (working copy) @@ -69,7 +69,8 @@ gc->setLabel(QObject::tr("Directory to hold recordings")); gc->setValue("/mnt/store/"); gc->setHelpText(QObject::tr("All recordings get stored in this " - "directory.")); + "directory (multiple directories can be specified, " + "seperated by commas).")); return gc; };