#include #include #include "controllers/controllers.h" #include "dto/dto.h" char windows_invalid_chars[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F<>:\"/\\|"; namespace api { uint64_t fs::next_temp_id = 0; std::unordered_map fs::zip_to_temp_map; std::unordered_map> fs::in_progress_zips; std::optional fs::get_node(uint64_t node) { db::MapperInode inode_mapper(drogon::app().getDbClient()); try { return inode_mapper.findByPrimaryKey(node); } catch (const std::exception&) { return std::nullopt; } } std::optional fs::get_node_and_validate(const db::User &user, uint64_t node) { auto inode = get_node(node); if (!inode.has_value()) return std::nullopt; if (inode->getValueOfOwnerId() != user.getValueOfId()) return std::nullopt; return inode; } std::vector fs::get_children(const db::INode& parent) { db::MapperInode inode_mapper(drogon::app().getDbClient()); return inode_mapper.findBy(db::Criteria(db::INode::Cols::_parent_id, db::CompareOps::EQ, parent.getValueOfId())); } std::variant> fs::create_node(std::string name, const db::User& owner, bool file, const std::optional &parent, bool force) { // Stolen from https://github.com/boostorg/filesystem/blob/develop/src/portability.cpp if (!force) if (name.empty() || name[0] == ' ' || name.find_first_of(windows_invalid_chars, 0, sizeof(windows_invalid_chars)) != std::string::npos || *(name.end() - 1) == ' ' || *(name.end() - 1) == '.' || name == "." || name == "..") return {create_node_error::INVALID_NAME}; db::INode node; node.setIsFile(file ? 1 : 0); node.setName(name); node.setOwnerId(owner.getValueOfId()); node.setHasPreview(0); if (parent.has_value()) { auto parent_node = get_node_and_validate(owner, *parent); if (!parent_node.has_value()) return {create_node_error::INVALID_PARENT}; if (parent_node->getValueOfIsFile() != 0) return {create_node_error::FILE_PARENT}; auto children = get_children(*parent_node); for (const auto& child : children) if (child.getValueOfName() == name) return {std::make_tuple( child.getValueOfIsFile() != 0, child.getValueOfId() )}; node.setParentId(*parent); } db::MapperInode inode_mapper(drogon::app().getDbClient()); inode_mapper.insert(node); return {node}; } void fs::delete_node(db::INode node, msd::channel& chan, bool allow_root) { if (node.getValueOfParentId() == 0 && (!allow_root)) return; db::MapperInode inode_mapper(drogon::app().getDbClient()); const auto delete_file = [&chan, &inode_mapper](const db::INode& node) { std::string entry = "Deleting "; generate_path(node, entry); entry >> chan; std::filesystem::path p("./files"); p /= std::to_string(node.getValueOfId()); std::filesystem::remove(p); if (node.getValueOfHasPreview() != 0) std::filesystem::remove(p.string() + "_preview.png"); inode_mapper.deleteOne(node); std::string(" Done\n") >> chan; }; std::stack queue, files, folders; if (node.getValueOfIsFile() == 0) queue.push(node); else files.push(node); while (!queue.empty()) { while (!files.empty()) { delete_file(files.top()); files.pop(); } std::string entry = "Deleting "; generate_path(queue.top(), entry); entry += "\n"; entry >> chan; auto children = get_children(queue.top()); folders.push(queue.top()); queue.pop(); for (const auto& child : children) { if (child.getValueOfIsFile() == 0) queue.push(child); else files.push(child); } } while (!files.empty()) { delete_file(files.top()); files.pop(); } while (!folders.empty()) { inode_mapper.deleteOne(folders.top()); folders.pop(); } } std::shared_ptr fs::get_user_mutex(uint64_t user_id) { static std::unordered_map> mutexes; static std::mutex mutexes_mutex; std::lock_guard guard(mutexes_mutex); return (*mutexes.try_emplace(user_id, std::make_shared()).first).second; } trantor::EventLoop* fs::get_zip_loop() { static bool init_done = false; static trantor::EventLoopThread loop("ZipEventLoop"); if (!init_done) { init_done = true; loop.run(); loop.getLoop()->runEvery(30*60, []{ for (const auto& entry : std::filesystem::directory_iterator("./temp")) { if (!entry.is_regular_file()) continue; const std::string file_name = "./temp/" + entry.path().filename().string(); const auto& progress_pos = std::find_if(in_progress_zips.begin(), in_progress_zips.end(), [&file_name](const std::pair>& entry) { return std::get<0>(entry.second) == file_name; } ); if (progress_pos != in_progress_zips.end()) return; const auto& zip_map_pos = std::find_if(zip_to_temp_map.begin(), zip_to_temp_map.end(), [&file_name](const std::pair& entry){ return entry.second == file_name; } ); if (zip_map_pos != zip_to_temp_map.end()) return; std::filesystem::remove(entry.path()); } }); } return loop.getLoop(); } trantor::EventLoop* fs::get_delete_loop() { static bool init_done = false; static trantor::EventLoopThread loop("DeleteEventLoop"); if (!init_done) { init_done = true; loop.run(); } return loop.getLoop(); } void fs::generate_path(db::INode node, std::string& str) { db::MapperInode inode_mapper(drogon::app().getDbClient()); std::stack path; path.push(node); while (node.getParentId() != nullptr) { node = inode_mapper.findByPrimaryKey(node.getValueOfParentId()); path.push(node); } while (!path.empty()) { const db::INode& seg = path.top(); str += seg.getValueOfName(); if (seg.getValueOfIsFile() == 0) str += "/"; path.pop(); } } Json::Value fs::generate_path(db::INode node) { Json::Value segments = Json::Value(Json::ValueType::arrayValue); db::MapperInode inode_mapper(drogon::app().getDbClient()); std::stack path; path.push(node); while (node.getParentId() != nullptr) { node = inode_mapper.findByPrimaryKey(node.getValueOfParentId()); path.push(node); } while (!path.empty()) { const db::INode& seg = path.top(); if (seg.getParentId() == nullptr) { Json::Value json_seg; json_seg["path"] = "/"; json_seg["node"] = seg.getValueOfId(); segments.append(json_seg); } else { Json::Value json_seg; json_seg["path"] = seg.getValueOfName(); json_seg["node"] = seg.getValueOfId(); segments.append(json_seg); if (seg.getValueOfIsFile() == 0) { json_seg.removeMember("node"); json_seg["path"] = "/"; segments.append(json_seg); } } path.pop(); } Json::Value resp; resp["segments"] = segments; return resp; } uint64_t fs::calc_total_size(const db::INode& base) { uint64_t size = 0; std::stack queue; queue.push(base); while (!queue.empty()) { const db::INode& node = queue.top(); if (node.getValueOfIsFile() == 0) { auto children = api::fs::get_children(node); queue.pop(); for (const auto& child : children) { if (child.getValueOfIsFile() == 0) queue.push(child); else if (child.getSize()) size += child.getValueOfSize(); } } else { size += node.getValueOfSize(); queue.pop(); } } return size; } void fs::add_to_zip(struct zip_t* zip, const std::string& key, const db::INode& node, const std::string& path) { if (node.getValueOfIsFile() == 0) { std::string new_path = path + node.getValueOfName() + "/"; zip_entry_opencasesensitive(zip, new_path.c_str()); zip_entry_close(zip); auto children = api::fs::get_children(node); for (const auto& child : children) add_to_zip(zip, key, child, new_path); } else { zip_entry_opencasesensitive(zip, (path + node.getValueOfName()).c_str()); std::ifstream file("./files/" + std::to_string(node.getValueOfId()), std::ifstream::binary); std::vector buffer(64*1024); while (!file.eof()) { file.read(buffer.data(), (std::streamsize)buffer.size()); auto read = file.gcount(); zip_entry_write(zip, buffer.data(), read); std::get<1>(in_progress_zips[key]) += read; } zip_entry_close(zip); } } }