diff options
author | randomdan <randomdan@localhost> | 2013-12-16 16:28:38 +0000 |
---|---|---|
committer | randomdan <randomdan@localhost> | 2013-12-16 16:28:38 +0000 |
commit | a4e9a6cd6549f94f4317b281eceb07cae647b4dd (patch) | |
tree | 7c3d05f33c6a62759f70dcb460e30168cd27b4b1 /p2pvr/lib/storage.cpp | |
parent | Don't specify the owner in the schema (diff) | |
download | p2pvr-a4e9a6cd6549f94f4317b281eceb07cae647b4dd.tar.bz2 p2pvr-a4e9a6cd6549f94f4317b281eceb07cae647b4dd.tar.xz p2pvr-a4e9a6cd6549f94f4317b281eceb07cae647b4dd.zip |
Add implementations of storage and recordings interfaces
Diffstat (limited to 'p2pvr/lib/storage.cpp')
-rw-r--r-- | p2pvr/lib/storage.cpp | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/p2pvr/lib/storage.cpp b/p2pvr/lib/storage.cpp new file mode 100644 index 0000000..9d9345d --- /dev/null +++ b/p2pvr/lib/storage.cpp @@ -0,0 +1,275 @@ +#include <pch.hpp> +#include "storage.h" +#include "fileSink.h" +#include <commonHelpers.h> +#include <fcntl.h> +#include <logger.h> +#include <boost/filesystem/operations.hpp> +#include <boost/uuid/uuid_generators.hpp> +#include <boost/uuid/uuid_io.hpp> +#include <boost/lexical_cast.hpp> + +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 <typename Arg> +inline static +fs::path +operator/(const fs::path & lhs, const IceUtil::Optional<Arg> & rhs) +{ + if (rhs) { + return lhs / *rhs; + } + return lhs; +} + +template <typename Arg, typename ... Args> +inline static +fs::path createPath(const fs::path & start, const Arg & arg, const Args & ... args) +{ + auto path = start / arg; + return createPath(path, args...); +} + +template <typename Arg, typename ... Args> +inline static +std::string firstNotNull(const Arg & arg, const Args & ...) +{ + return boost::lexical_cast<std::string>(arg); +} + +template <typename Arg, typename ... Args> +inline static +std::string firstNotNull(const IceUtil::Optional<Arg> & arg, const Args & ... args) +{ + return arg ? firstNotNull(*arg, args...) : firstNotNull(args...); +} + +template <typename ... Args> +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 <typename Arg, typename ... Args> +inline static +IceUtil::Optional<std::string> +formatIf(boost::format & f, const IceUtil::Optional<Arg> & arg, const Args & ... args) +{ + if (arg) { + f % *arg; + return formatIf(f, args...); + } + else { + return IceUtil::Optional<std::string>(); + } +} + +template <typename Arg, typename ... Args> +inline static +IceUtil::Optional<std::string> +formatIf(boost::format & f, const Arg & arg, const Args & ... args) +{ + f % arg; + return formatIf(f, args...); +} + +template <typename... Args> +inline static +IceUtil::Optional<std::string> +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<std::string>(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); + } +} + |