summaryrefslogtreecommitdiff
path: root/p2pvr/lib/storage.cpp
diff options
context:
space:
mode:
authorrandomdan <randomdan@localhost>2013-12-16 16:28:38 +0000
committerrandomdan <randomdan@localhost>2013-12-16 16:28:38 +0000
commita4e9a6cd6549f94f4317b281eceb07cae647b4dd (patch)
tree7c3d05f33c6a62759f70dcb460e30168cd27b4b1 /p2pvr/lib/storage.cpp
parentDon't specify the owner in the schema (diff)
downloadp2pvr-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.cpp275
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);
+ }
+}
+