#include #include "storage.h" #include "fileSink.h" #include #include #include #include #include #include #include namespace fs = boost::filesystem; fs::path Storage::root; fs::path Storage::byAll; fs::path Storage::byTitle; fs::path Storage::byDate; fs::path Storage::byService; fs::path Storage::bySchedule; DECLARE_OPTIONS(Storage, "P2PVR Storage options") ("p2pvr.storage.root", Options::value(&root, "recordings"), "Root folder in which to store recordings") ("p2pvr.storage.all", Options::value(&byAll, "all"), "Sub folder in which to store all recordings") ("p2pvr.storage.bytitle", Options::value(&byTitle, "title"), "Sub folder in which to store by title") ("p2pvr.storage.bydate", Options::value(&byDate, "date"), "Sub folder in which to store by date") ("p2pvr.storage.byservice", Options::value(&byService, "service"), "Sub folder in which to store by service") ("p2pvr.storage.byschedule", Options::value(&bySchedule, "schedule"), "Sub folder in which to store by schedule") END_OPTIONS(Storage); inline static fs::path createPath(const fs::path & start) { return start; } template inline static fs::path operator/(const fs::path & lhs, const IceUtil::Optional & rhs) { if (rhs) { return lhs / *rhs; } return lhs; } template inline static fs::path createPath(const fs::path & start, const Arg & arg, const Args & ... args) { auto path = start / arg; return createPath(path, args...); } template inline static std::string firstNotNull(const Arg & arg, const Args & ...) { return boost::lexical_cast(arg); } template inline static std::string firstNotNull(const IceUtil::Optional & arg, const Args & ... args) { return arg ? firstNotNull(*arg, args...) : firstNotNull(args...); } template inline static std::string firstNotNull(const std::string & arg, const Args & ...) { return arg; } inline static std::string formatIf(const boost::format & f) { return f.str(); } template inline static IceUtil::Optional formatIf(boost::format & f, const IceUtil::Optional & arg, const Args & ... args) { if (arg) { f % *arg; return formatIf(f, args...); } else { return IceUtil::Optional(); } } template inline static IceUtil::Optional formatIf(boost::format & f, const Arg & arg, const Args & ... args) { f % arg; return formatIf(f, args...); } template inline static IceUtil::Optional formatIf(const std::string & msgfmt, const Args & ... args) { boost::format fmt(msgfmt); return formatIf(fmt, args...); } std::string Storage::CreateForEventRecording(const std::string & ext, const P2PVR::SchedulePtr & schedule, const DVBSI::ServicePtr & service, const DVBSI::EventPtr & event, const Ice::Current &) { fs::create_directories(root / byAll); auto id = boost::lexical_cast(boost::uuids::random_generator()()); fs::path path = root / byAll / id; path.replace_extension(ext); auto fd = open(path.string().c_str(), O_WRONLY | O_CREAT | O_EXCL, 0664); if (fd < 0) { Logger()->messagebf(LOG_ERR, "%s: Failed to open file for writing at %s (%d:%s)", __PRETTY_FUNCTION__, path, errno, strerror(errno)); throw P2PVR::StorageException(path.string(), strerror(errno)); } createSymlinks(ext, id, schedule, service, event); Logger()->messagebf(LOG_INFO, "%s: Created new recording %s", __PRETTY_FUNCTION__, path); close(fd); return id; } void Storage::createSymlinks(const std::string & ext, const std::string & target, const P2PVR::SchedulePtr & schedule, const DVBSI::ServicePtr & service, const DVBSI::EventPtr & event) { // By title, with optional season and episode information createSymlink(ext, target, createPath(root, byTitle, event->Title, formatIf("Season %02d", event->Season), firstNotNull( formatIf("Part %02d of %02d - %s", event->Episode, event->Episodes, event->Subtitle), formatIf("Episode %02d - %s", event->Episode, event->Subtitle), formatIf("Part %02d of %02d - %s", event->Episode, event->Episodes, event->Description), formatIf("Part %02d of %02d", event->Episode, event->Episodes), formatIf("Episode %02d - %s", event->Episode, event->Description), event->Subtitle, event->Description, formatIf("Episode %02d", event->Episode), event->StartTime))); // By date createSymlink(ext, target, createPath(root, byDate, formatIf("%04d-%02d-%02d", event->StartTime.Year, event->StartTime.Month, event->StartTime.Day), firstNotNull( formatIf("%s - %s", event->Title, event->Subtitle), formatIf("%s - %s", event->Title, event->Description), event->Title))); // By service createSymlink(ext, target, createPath(root, byService, service->Name, firstNotNull( formatIf("%s - %s", event->Title, event->Subtitle), formatIf("%s - %s", event->Title, event->Description), event->Title))); // By schedule title createSymlink(ext, target, createPath(root, bySchedule, formatIf("Title: %s", schedule->Title), formatIf("%04d-%02d-%02d", event->StartTime.Year, event->StartTime.Month, event->StartTime.Day), firstNotNull( formatIf("%s - %s", event->Title, event->Subtitle), formatIf("%s - %s", event->Title, event->Description), event->Title))); // By schedule search createSymlink(ext, target, createPath(root, bySchedule, formatIf("Search: %s", schedule->Search), firstNotNull( formatIf("%s - %s", event->Title, event->Subtitle), formatIf("%s - %s", event->Title, event->Description), event->Title))); } void Storage::createSymlink(const std::string & ext, const std::string & target, const fs::path & link) { fs::path path = link; path.replace_extension(ext); Logger()->messagebf(LOG_DEBUG, "%s: link(%s) -> target(%s)", __PRETTY_FUNCTION__, path, target); if (fs::exists(path)) { Logger()->messagebf(LOG_WARNING, "%s: symlink already exists %s", __PRETTY_FUNCTION__, path); return; } fs::create_directories(path.parent_path()); fs::path relativeTarget; for (fs::path tmp = path.parent_path(); tmp != root; tmp = tmp.parent_path()) { relativeTarget /= ".."; } relativeTarget /= byAll; relativeTarget /= target; relativeTarget.replace_extension(ext); fs::create_symlink(relativeTarget, path); } std::string Storage::FindExtension(const std::string & id) { fs::directory_iterator end; for (fs::directory_iterator itr(root / byAll); itr != end; ++itr) { if (itr->path().stem() == id) { return itr->path().extension().string(); } } return ""; } P2PVR::RawDataClientPrx Storage::OpenForWrite(const std::string & id, const Ice::Current & ice) { fs::path path = root / byAll / id; path.replace_extension(FindExtension(id)); auto fd = open(path.string().c_str(), O_WRONLY | O_APPEND | O_LARGEFILE); if (fd < 0) { Logger()->messagebf(LOG_ERR, "%s: Failed to open file for reading at %s (%d:%s)", __PRETTY_FUNCTION__, path, errno, strerror(errno)); throw P2PVR::StorageException(path.string(), strerror(errno)); } auto openFile = OpenFilePtr(new OpenFile(ice.adapter, new FileSink(fd))); openFiles.insert(openFile); return *openFile; } void Storage::Close(const P2PVR::RawDataClientPrx & file, const Ice::Current &) { openFiles.erase(std::find_if(openFiles.begin(), openFiles.end(), [&file](const OpenFilePtr & of) { return *of == file; })); } void Storage::Delete(const std::string & id, const Ice::Current &) { fs::path path = root / byAll / id; path.replace_extension(FindExtension(id)); Logger()->messagebf(LOG_INFO, "%s: Deleting links to %s", __PRETTY_FUNCTION__, path); DeleteFrom(path, fs::canonical(root)); } void Storage::DeleteFrom(const fs::path & path, const fs::path & from) { Logger()->messagebf(LOG_DEBUG, "%s: Deleting links to %s to %s", __PRETTY_FUNCTION__, path, from); fs::directory_iterator end; for (fs::directory_iterator itr(from); itr != end; ++itr) { if (fs::is_directory(*itr)) { DeleteFrom(path, *itr); } else { boost::system::error_code err; auto link = fs::canonical(*itr, err); if (err || link == path) { Logger()->messagebf(LOG_DEBUG, "%s: deleting %s", __PRETTY_FUNCTION__, *itr); fs::remove(*itr); } } } if (from != root && fs::is_empty(from)) { Logger()->messagebf(LOG_DEBUG, "%s: deleting directory %s", __PRETTY_FUNCTION__, from); fs::remove(from); } }