diff --git a/FS.cpp b/FS.cpp new file mode 100644 index 00000000..758b81c8 --- /dev/null +++ b/FS.cpp @@ -0,0 +1,189 @@ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include + +#include "Base.h" +#include "FS.h" +#include "Log.h" + +namespace i2p { +namespace fs { + std::string appName = "i2pd"; + std::string dataDir = ""; +#ifdef _WIN32 + std::string dirSep = "\\"; +#else + std::string dirSep = "/"; +#endif + HashedStorage NetDB("netDb", "r", "routerInfo-", "dat"); + HashedStorage Peers("peerProfiles", "p", "profile-", "txt"); + ABookStorage ABook("addressbook", "b", "", "b32"); + + static const char T32[32] = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'k', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 't', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '2', '3', '4', '5', '6', '7', + }; + static const char T64[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '-', '~' + }; + + const std::string & GetAppName () { + return appName; + } + + void SetAppName (const std::string& name) { + appName = name; + } + + const std::string & GetDataDir () { + return dataDir; + } + + void DetectDataDir(const std::string & cmdline_param, bool isService) { + if (cmdline_param != "") { + dataDir = cmdline_param; + return; + } +#if defined(WIN32) || defined(_WIN32) + char localAppData[MAX_PATH]; + SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); + dataDir = std::string(localAppData) + "\\" + appName; + return; +#elif defined(MAC_OSX) + char *home = getenv("HOME"); + dataDir = (home != NULL && strlen(home) > 0) ? home : ""; + dataDir += "/Library/Application Support/" + appName; + return; +#else /* other unix */ + char *home = getenv("HOME"); + if (isService) { + dataDir = "/var/lib/" + appName; + } else if (home != NULL && strlen(home) > 0) { + dataDir = std::string(home) + "/." + appName; + } else { + dataDir = "/tmp/" + appName; + } + return; +#endif + } + + bool Init() { + if (boost::filesystem::exists(dataDir)) + boost::filesystem::create_directory(dataDir); + std::string destinations = DataDirPath("destinations"); + if (boost::filesystem::exists(destinations)) + boost::filesystem::create_directory(destinations); + + NetDB.SetRoot(dataDir); + NetDB.Init(T64, 64); + Peers.SetRoot(dataDir); + Peers.Init(T64, 64); + ABook.SetRoot(dataDir); + ABook.Init(T32, 32); + return true; + } + + bool ReadDir(const std::string & path, std::vector & files) { + if (!boost::filesystem::exists(path)) + return false; + boost::filesystem::directory_iterator it(path); + boost::filesystem::directory_iterator end; + + for ( ; it != end; it++) { + if (!boost::filesystem::is_regular_file(it->status())) + continue; + files.push_back(it->path().string()); + } + + return true; + } + + bool Exists(const std::string & path) { + return boost::filesystem::exists(path); + } + + bool Remove(const std::string & path) { + if (!boost::filesystem::exists(path)) + return false; + return boost::filesystem::remove(path); + } + + void HashedStorage::SetRoot(const std::string &path) { + root = path + i2p::fs::dirSep + name; + } + + bool HashedStorage::Init(const char * chars, size_t count) { + if (!boost::filesystem::exists(root)) { + boost::filesystem::create_directory(root); + } + + for (size_t i = 0; i < count; i++) { + auto p = root + i2p::fs::dirSep + prefix1 + chars[i]; + if (boost::filesystem::exists(p)) + continue; + if (boost::filesystem::create_directory(p)) + continue; /* ^ throws exception on failure */ + return false; + } + return true; + } + + std::string HashedStorage::Path(const std::string & ident) { + std::string safe_ident = ident; + std::replace(safe_ident.begin(), safe_ident.end(), '/', '-'); + std::replace(safe_ident.begin(), safe_ident.end(), '\\', '-'); + + std::stringstream t(""); + t << this->root << i2p::fs::dirSep; + t << prefix1 << safe_ident[0] << i2p::fs::dirSep; + t << prefix2 << safe_ident << "." << suffix; + + return t.str(); + } + + void HashedStorage::Remove(const std::string & ident) { + std::string path = Path(ident); + if (!boost::filesystem::exists(path)) + return; + boost::filesystem::remove(path); + } + + void HashedStorage::Traverse(std::vector & files) { + boost::filesystem::path p(root); + boost::filesystem::recursive_directory_iterator it(p); + boost::filesystem::recursive_directory_iterator end; + + for ( ; it != end; it++) { + if (!boost::filesystem::is_regular_file( it->status() )) + continue; + const std::string & t = it->path().string(); + files.push_back(t); + } + } + + std::string ABookStorage::IndexPath() { + std::string path = root + i2p::fs::dirSep + "addresses.csv"; + return path; + } + + HashedStorage & GetNetDB() { return NetDB; } + HashedStorage & GetPeerProfiles() { return Peers; } + ABookStorage & GetAddressBook() { return ABook; } +} // fs +} // i2p diff --git a/FS.h b/FS.h new file mode 100644 index 00000000..80b07353 --- /dev/null +++ b/FS.h @@ -0,0 +1,143 @@ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include + +namespace i2p { +namespace fs { + extern std::string dirSep; + + /** + * @brief Class to work with NetDb & Router profiles + * + * Usage: + * + * const char alphabet[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; + * auto h = HashedStorage("name", "y", "z-", ".txt"); + * h.SetRoot("/tmp/hs-test"); + * h.Init(alphabet, 8); <- creates needed dirs, 8 is size of alphabet + * h.Path("abcd"); <- returns /tmp/hs-test/name/ya/z-abcd.txt + * h.Remove("abcd"); <- removes /tmp/hs-test/name/ya/z-abcd.txt, if it exists + * std::vector files; + * h.Traverse(files); <- finds all files in storage and saves in given vector + */ + class HashedStorage { + protected: + std::string root; + std::string name; + std::string prefix1; + std::string prefix2; + std::string suffix; + + public: + HashedStorage(const char *n, const char *p1, const char *p2, const char *s): + name(n), prefix1(p1), prefix2(p2), suffix(s) {}; + + bool Init(const char* chars, size_t cnt); + const std::string & GetRoot() { return this->root; } + void SetRoot(const std::string & path); + std::string Path(const std::string & ident); + void Remove(const std::string & ident); + void Traverse(std::vector & files); + }; + + /** @brief Slightly extended HashedStorage */ + class ABookStorage : public HashedStorage { + public: + ABookStorage(const char *n, const char *p1, const char *p2, const char *s): + HashedStorage(n, p1, p2, s) {}; + + std::string IndexPath(); + }; + + /** @brief Returns current application name, default 'i2pd' */ + const std::string & GetAppName (); + /** @brief Set applicaton name, affects autodetection of datadir */ + void SetAppName (const std::string& name); + + /** @brief Returns datadir path */ + const std::string & GetDataDir(); + + /** + * @brief Set datadir either from cmdline option or using autodetection + * @param cmdline_param Value of cmdline parameter --datadir= + * @param isService Value of cmdline parameter --service + * + * Examples of autodetected paths: + * + * Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\ + * Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\ + * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/ + * Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/ + */ + void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); + + /** + * @brief Create subdirectories inside datadir + */ + bool Init(); + + /** + * @brief Get list of files in directory + * @param path Path to directory + * @param files Vector to store found files + * @return true on success and false if directory not exists + */ + bool ReadDir(const std::string & path, std::vector & files); + + /** + * @brief Remove file with given path + * @param path Absolute path to file + * @return true on success, false if file not exists, throws exception on error + */ + bool Remove(const std::string & path); + + /** + * @brief Check existence of file + * @param path Absolute path to file + * @return true if file exists, false otherwise + */ + bool Exists(const std::string & path); + + template + void _ExpandPath(std::stringstream & path, T c) { + path << i2p::fs::dirSep << c; + } + + template + void _ExpandPath(std::stringstream & path, T c, Other ... other) { + _ExpandPath(path, c); + _ExpandPath(path, other ...); + } + + /** + * @brief Get path relative to datadir + * + * Examples (with datadir = "/tmp/i2pd"): + * + * i2p::fs::Path("test") -> '/tmp/i2pd/test' + * i2p::fs::Path("test", "file.txt") -> '/tmp/i2pd/test/file.txt' + */ + template + std::string DataDirPath(Other ... components) { + std::stringstream s(""); + s << i2p::fs::GetDataDir(); + _ExpandPath(s, components ...); + + return s.str(); + } + + /* accessors */ + HashedStorage & GetNetDB(); + HashedStorage & GetPeerProfiles(); + ABookStorage & GetAddressBook(); +} // fs +} // i2p