Index: mythtv-trunk/libs/libmythtv/RingBuffer.cpp =================================================================== --- mythtv-trunk.orig/libs/libmythtv/RingBuffer.cpp +++ mythtv-trunk/libs/libmythtv/RingBuffer.cpp @@ -163,23 +163,33 @@ void RingBuffer::OpenFile(const QString 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); - - QFile checkFile(local_pathname); - if (checkFile.exists()) + // 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) { - is_local = true; - filename = local_pathname; + if (!found) + { + local_pathname = recording_prefixes[index] + filename.right(filename.length() - hostlen); + + QFile checkFile(local_pathname); + if (checkFile.exists()) + { + found = true; + is_local = true; + filename = local_pathname; + } + } } } } Index: mythtv-trunk/libs/libmythtv/previewgenerator.cpp =================================================================== --- mythtv-trunk.orig/libs/libmythtv/previewgenerator.cpp +++ mythtv-trunk/libs/libmythtv/previewgenerator.cpp @@ -65,8 +65,22 @@ PreviewGenerator::PreviewGenerator(const // 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(); Index: mythtv-trunk/libs/libmythtv/programinfo.cpp =================================================================== --- mythtv-trunk.orig/libs/libmythtv/programinfo.cpp +++ mythtv-trunk/libs/libmythtv/programinfo.cpp @@ -1464,6 +1464,23 @@ QString ProgramInfo::GetRecordBasename(v */ 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: mythtv-trunk/libs/libmythtv/tv_rec.cpp =================================================================== --- mythtv-trunk.orig/libs/libmythtv/tv_rec.cpp +++ mythtv-trunk/libs/libmythtv/tv_rec.cpp @@ -642,8 +642,29 @@ void TVRec::StartedRecording(ProgramInfo 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: mythtv-trunk/programs/mythbackend/autoexpire.cpp =================================================================== --- mythtv-trunk.orig/programs/mythbackend/autoexpire.cpp +++ mythtv-trunk/programs/mythbackend/autoexpire.cpp @@ -115,10 +115,32 @@ void AutoExpire::CalcParams(vectorIsConnected()) { + + // 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) @@ -221,7 +243,7 @@ static inline bool operator!= (const fsi * * 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 @@ -330,20 +352,25 @@ void AutoExpire::ExpireLiveTV(int type) long long availFreeKB = 0; long long tKB, uKB; - if ((availFreeKB = getDiskSpace(record_file_prefix, tKB, uKB)) < 0) - { - QString msg = QString("ERROR: Could not calculate free space for %1.") - .arg(record_file_prefix); - VERBOSE(VB_IMPORTANT, LOC + msg); - gContext->LogEntry("mythbackend", LP_WARNING, - "Autoexpire Recording", msg); - } - else + QStringList recording_prefixes = QStringList::split(",",record_file_prefix); + VERBOSE(VB_FILE, LOC + QString("ExpireLiveTV(%1)").arg(type)); + for (QStringList::const_iterator it = recording_prefixes.begin(); + it != recording_prefixes.end(); ++it) { - VERBOSE(VB_FILE, LOC + QString("ExpireLiveTV(%1)").arg(type)); - ClearExpireList(); - FillDBOrdered(type); - SendDeleteMessages(availFreeKB, 0, true); + if ((availFreeKB = getDiskSpace(*it, tKB, uKB)) < 0) + { + QString msg = QString("ERROR: Could not calculate free space for %1.") + .arg(*it); + VERBOSE(VB_IMPORTANT, LOC + msg); + gContext->LogEntry("mythbackend", LP_WARNING, + "Autoexpire Recording", msg); + } + else + { + ClearExpireList(); + FillDBOrdered(type); + SendDeleteMessages(availFreeKB, 0, *it, true); + } } } @@ -356,39 +383,47 @@ void AutoExpire::ExpireRecordings(void) long long availFreeKB = 0; long long tKB, uKB; - if ((availFreeKB = getDiskSpace(record_file_prefix, tKB, uKB)) < 0) - { - 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) + // 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) { - 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()) { @@ -402,7 +437,8 @@ static bool CheckFile(const ProgramInfo 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. " @@ -427,36 +463,46 @@ static bool CheckFile(const ProgramInfo 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 QString titlestr = (*it)->title; @@ -475,9 +521,10 @@ void AutoExpire::SendDeleteMessages(size } 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); @@ -510,7 +557,7 @@ void AutoExpire::SendDeleteMessages(size 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); @@ -663,7 +710,7 @@ void AutoExpire::FillExpireList(void) case emLowestPriorityFirst: case emWeightedTimePriority: FillDBOrdered(expMethod); - break; + break; // default falls through so list is empty so no AutoExpire } } Index: mythtv-trunk/programs/mythbackend/autoexpire.h =================================================================== --- mythtv-trunk.orig/programs/mythbackend/autoexpire.h +++ mythtv-trunk/programs/mythbackend/autoexpire.h @@ -60,7 +60,7 @@ class AutoExpire : public QObject 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); void WaitForPendingTruncates(void); Index: mythtv-trunk/programs/mythbackend/encoderlink.cpp =================================================================== --- mythtv-trunk.orig/programs/mythbackend/encoderlink.cpp +++ mythtv-trunk/programs/mythbackend/encoderlink.cpp @@ -243,7 +243,21 @@ long long EncoderLink::GetFreeDiskSpace( 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: mythtv-trunk/programs/mythbackend/main.cpp =================================================================== --- mythtv-trunk.orig/programs/mythbackend/main.cpp +++ mythtv-trunk/programs/mythbackend/main.cpp @@ -45,7 +45,6 @@ AutoExpire *expirer = NULL; Scheduler *sched = NULL; JobQueue *jobqueue = NULL; QString pidfile; -QString lockfile_location; HouseKeeper *housekeeping = NULL; QString logfile = ""; @@ -216,7 +215,12 @@ void cleanup(void) 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); } @@ -648,8 +652,6 @@ int main(int argc, char **argv) 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 @@ -658,14 +660,19 @@ int main(int argc, char **argv) // 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: mythtv-trunk/programs/mythbackend/mainserver.cpp =================================================================== --- mythtv-trunk.orig/programs/mythbackend/mainserver.cpp +++ mythtv-trunk/programs/mythbackend/mainserver.cpp @@ -1213,16 +1213,30 @@ void MainServer::HandleQueryRecordings(Q 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; @@ -2011,7 +2025,16 @@ void MainServer::HandleQueryFreeSpace(Pl { 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); @@ -3854,8 +3877,9 @@ QString MainServer::LocalFilePath(QUrl & } 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)); Index: mythtv-trunk/programs/mythtv-setup/backendsettings.cpp =================================================================== --- mythtv-trunk.orig/programs/mythtv-setup/backendsettings.cpp +++ mythtv-trunk/programs/mythtv-setup/backendsettings.cpp @@ -70,7 +70,8 @@ static HostLineEdit *RecordFilePrefix() 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; }; Index: mythtv-trunk/programs/mythtv-setup/checksetup.cpp =================================================================== --- mythtv-trunk.orig/programs/mythtv-setup/checksetup.cpp +++ mythtv-trunk/programs/mythtv-setup/checksetup.cpp @@ -60,8 +60,13 @@ bool checkStoragePaths(QString *probs) 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: mythtv-trunk/programs/mythbackend/httpstatus.cpp =================================================================== --- mythtv-trunk.orig/programs/mythbackend/httpstatus.cpp +++ mythtv-trunk/programs/mythbackend/httpstatus.cpp @@ -1283,9 +1283,18 @@ void HttpStatus::FillStatusXML( QDomDocu // drive space --------------------- long long iTotal = -1, iUsed = -1, iAvail = -1; - - iAvail = getDiskSpace( gContext->GetFilePrefix(), iTotal, iUsed); - + long long cur_iTotal = -1, cur_iUsed = -1, cur_iAvail = -1; + + QStringList recording_prefixes = QStringList::split(",", gContext->GetSetting("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)); storage.setAttribute("_local_free" , (int)(iAvail>>10));