Merge branch '22-mutex-on-fs-operations' into 'main'
Resolve "Mutex on fs operations" Closes #22 See merge request root/fileserver!9
This commit is contained in:
commit
c8e9b11d83
@ -21,9 +21,11 @@ add_executable(backend
|
|||||||
|
|
||||||
src/controllers/controllers.h
|
src/controllers/controllers.h
|
||||||
src/controllers/admin.cpp
|
src/controllers/admin.cpp
|
||||||
src/controllers/fs.cpp
|
|
||||||
src/controllers/user.cpp
|
src/controllers/user.cpp
|
||||||
|
|
||||||
|
src/controllers/fs/fs_routes.cpp
|
||||||
|
src/controllers/fs/fs_functions.cpp
|
||||||
|
|
||||||
src/controllers/auth/auth_common.cpp
|
src/controllers/auth/auth_common.cpp
|
||||||
src/controllers/auth/auth_basic.cpp
|
src/controllers/auth/auth_basic.cpp
|
||||||
src/controllers/auth/auth_2fa.cpp
|
src/controllers/auth/auth_2fa.cpp
|
||||||
|
@ -62,6 +62,9 @@ namespace api {
|
|||||||
db::MapperUser user_mapper(drogon::app().getDbClient());
|
db::MapperUser user_mapper(drogon::app().getDbClient());
|
||||||
auto user = user_mapper.findByPrimaryKey(user_id);
|
auto user = user_mapper.findByPrimaryKey(user_id);
|
||||||
auth::revoke_all(user);
|
auth::revoke_all(user);
|
||||||
|
|
||||||
|
std::unique_lock lock(*fs::get_user_mutex(user.getValueOfId()));
|
||||||
|
|
||||||
fs::delete_node(fs::get_node(user.getValueOfRootId()).value(), chan, true);
|
fs::delete_node(fs::get_node(user.getValueOfRootId()).value(), chan, true);
|
||||||
user_mapper.deleteOne(user);
|
user_mapper.deleteOne(user);
|
||||||
cbk(dto::Responses::get_success_res());
|
cbk(dto::Responses::get_success_res());
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
#ifndef BACKEND_CONTROLLERS_H
|
#ifndef BACKEND_CONTROLLERS_H
|
||||||
#define BACKEND_CONTROLLERS_H
|
#define BACKEND_CONTROLLERS_H
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <shared_mutex>
|
||||||
|
|
||||||
#include <drogon/drogon.h>
|
#include <drogon/drogon.h>
|
||||||
#include <botan/rng.h>
|
#include <botan/rng.h>
|
||||||
#include <msd/channel.hpp>
|
#include <msd/channel.hpp>
|
||||||
|
#include <trantor/net/EventLoopThread.h>
|
||||||
|
#include <kubazip/zip/zip.h>
|
||||||
|
|
||||||
#include "db/db.h"
|
#include "db/db.h"
|
||||||
|
|
||||||
@ -111,7 +115,7 @@ public:
|
|||||||
static std::variant<db::INode, fs::create_node_error, std::tuple<bool, uint64_t>>
|
static std::variant<db::INode, fs::create_node_error, std::tuple<bool, uint64_t>>
|
||||||
create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &parent, bool force = false);
|
create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &parent, bool force = false);
|
||||||
static void delete_node(db::INode node, msd::channel<std::string>& chan, bool allow_root = false);
|
static void delete_node(db::INode node, msd::channel<std::string>& chan, bool allow_root = false);
|
||||||
|
static std::shared_ptr<std::shared_mutex> get_user_mutex(uint64_t user_id);
|
||||||
|
|
||||||
void root(req_type, cbk_type);
|
void root(req_type, cbk_type);
|
||||||
void node(req_type, cbk_type, uint64_t node);
|
void node(req_type, cbk_type, uint64_t node);
|
||||||
@ -124,6 +128,18 @@ public:
|
|||||||
void download_multi(req_type, cbk_type);
|
void download_multi(req_type, cbk_type);
|
||||||
void download_preview(req_type, cbk_type, uint64_t node);
|
void download_preview(req_type, cbk_type, uint64_t node);
|
||||||
void get_type(req_type, cbk_type, uint64_t node);
|
void get_type(req_type, cbk_type, uint64_t node);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static trantor::EventLoop* get_zip_loop();
|
||||||
|
static trantor::EventLoop* get_delete_loop();
|
||||||
|
static void generate_path(db::INode node, std::string& str);
|
||||||
|
static Json::Value generate_path(db::INode node);
|
||||||
|
static uint64_t calc_total_size(const db::INode& base);
|
||||||
|
static void add_to_zip(struct zip_t* zip, const std::string& key, const db::INode& node, const std::string& path);
|
||||||
|
|
||||||
|
static uint64_t next_temp_id;
|
||||||
|
static std::unordered_map<std::string, std::string> zip_to_temp_map;
|
||||||
|
static std::unordered_map<std::string, std::tuple<std::string, uint64_t, uint64_t>> in_progress_zips;
|
||||||
};
|
};
|
||||||
|
|
||||||
class user : public drogon::HttpController<user> {
|
class user : public drogon::HttpController<user> {
|
||||||
|
257
backend/src/controllers/fs/fs_functions.cpp
Normal file
257
backend/src/controllers/fs/fs_functions.cpp
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#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<std::string, std::string> fs::zip_to_temp_map;
|
||||||
|
std::unordered_map<std::string, std::tuple<std::string, uint64_t, uint64_t>> fs::in_progress_zips;
|
||||||
|
|
||||||
|
std::optional<db::INode> 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<db::INode> 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<db::INode> 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<db::INode, fs::create_node_error, std::tuple<bool, uint64_t>>
|
||||||
|
fs::create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &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<std::string>& 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<db::INode> 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<std::shared_mutex> fs::get_user_mutex(uint64_t user_id) {
|
||||||
|
static std::unordered_map<uint64_t, std::shared_ptr<std::shared_mutex>> mutexes;
|
||||||
|
static std::mutex mutexes_mutex;
|
||||||
|
std::lock_guard guard(mutexes_mutex);
|
||||||
|
return (*mutexes.try_emplace(user_id, std::make_shared<std::shared_mutex>()).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<std::string, std::tuple<std::string, uint64_t, uint64_t>>& 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<std::string, std::string>& 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<db::INode> 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<db::INode> 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<db::INode> 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<char> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,12 @@
|
|||||||
#pragma clang diagnostic push
|
|
||||||
#pragma ide diagnostic ignored "performance-unnecessary-value-param"
|
|
||||||
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
|
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <unordered_map>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#include <opencv2/opencv.hpp>
|
#include <opencv2/opencv.hpp>
|
||||||
#include <botan/base64.h>
|
#include <botan/base64.h>
|
||||||
#include <trantor/net/EventLoopThread.h>
|
|
||||||
#include <zip/zip.h>
|
|
||||||
|
|
||||||
#include "controllers.h"
|
#include "controllers/controllers.h"
|
||||||
#include "dto/dto.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<>:\"/\\|";
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types#common_image_file_types
|
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types#common_image_file_types
|
||||||
const std::unordered_map<std::string, std::string> mime_type_map = {
|
const std::unordered_map<std::string, std::string> mime_type_map = {
|
||||||
{ ".apng" , "image/apng" },
|
{ ".apng" , "image/apng" },
|
||||||
@ -53,142 +44,6 @@ const std::unordered_map<std::string, std::string> mime_type_map = {
|
|||||||
{ ".mks" , "video/x-matroska" },
|
{ ".mks" , "video/x-matroska" },
|
||||||
};
|
};
|
||||||
|
|
||||||
uint64_t next_temp_id = 0;
|
|
||||||
std::unordered_map<std::string, std::string> zip_to_temp_map;
|
|
||||||
std::unordered_map<std::string, std::tuple<std::string, uint64_t, uint64_t>> in_progress_zips;
|
|
||||||
|
|
||||||
trantor::EventLoop* 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<std::string, std::tuple<std::string, uint64_t, uint64_t>>& 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<std::string, std::string>& 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* 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 generate_path(db::INode node, std::string& str) {
|
|
||||||
db::MapperInode inode_mapper(drogon::app().getDbClient());
|
|
||||||
std::stack<db::INode> 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 generate_path(db::INode node) {
|
|
||||||
Json::Value segments = Json::Value(Json::ValueType::arrayValue);
|
|
||||||
db::MapperInode inode_mapper(drogon::app().getDbClient());
|
|
||||||
std::stack<db::INode> 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 calc_total_size(const db::INode& base) {
|
|
||||||
uint64_t size = 0;
|
|
||||||
std::stack<db::INode> 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 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<char> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename InputIt>
|
template<typename InputIt>
|
||||||
std::string join_string(InputIt first, InputIt last, const std::string& separator = ",") {
|
std::string join_string(InputIt first, InputIt last, const std::string& separator = ",") {
|
||||||
std::ostringstream result;
|
std::ostringstream result;
|
||||||
@ -202,111 +57,6 @@ std::string join_string(InputIt first, InputIt last, const std::string& separato
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace api {
|
namespace api {
|
||||||
std::optional<db::INode> 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<db::INode> 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<db::INode> 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<db::INode, fs::create_node_error, std::tuple<bool, uint64_t>>
|
|
||||||
fs::create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &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<std::string>& 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<db::INode> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void fs::root(req_type req, cbk_type cbk) {
|
void fs::root(req_type req, cbk_type cbk) {
|
||||||
db::User user = dto::get_user(req);
|
db::User user = dto::get_user(req);
|
||||||
cbk(dto::Responses::get_root_res(user.getValueOfRootId()));
|
cbk(dto::Responses::get_root_res(user.getValueOfRootId()));
|
||||||
@ -314,6 +64,7 @@ namespace api {
|
|||||||
|
|
||||||
void fs::node(req_type req, cbk_type cbk, uint64_t node) {
|
void fs::node(req_type req, cbk_type cbk, uint64_t node) {
|
||||||
db::User user = dto::get_user(req);
|
db::User user = dto::get_user(req);
|
||||||
|
std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
|
||||||
auto inode = get_node_and_validate(user, node);
|
auto inode = get_node_and_validate(user, node);
|
||||||
if (!inode.has_value())
|
if (!inode.has_value())
|
||||||
return cbk(dto::Responses::get_badreq_res("Unknown node"));
|
return cbk(dto::Responses::get_badreq_res("Unknown node"));
|
||||||
@ -325,6 +76,7 @@ namespace api {
|
|||||||
|
|
||||||
void fs::path(req_type req, cbk_type cbk, uint64_t node) {
|
void fs::path(req_type req, cbk_type cbk, uint64_t node) {
|
||||||
db::User user = dto::get_user(req);
|
db::User user = dto::get_user(req);
|
||||||
|
std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
|
||||||
auto inode = get_node_and_validate(user, node);
|
auto inode = get_node_and_validate(user, node);
|
||||||
if (!inode.has_value())
|
if (!inode.has_value())
|
||||||
cbk(dto::Responses::get_badreq_res("Unknown node"));
|
cbk(dto::Responses::get_badreq_res("Unknown node"));
|
||||||
@ -342,6 +94,8 @@ namespace api {
|
|||||||
uint64_t parent = dto::json_get<uint64_t>(json, "parent").value();
|
uint64_t parent = dto::json_get<uint64_t>(json, "parent").value();
|
||||||
std::string name = dto::json_get<std::string>(json, "name").value();
|
std::string name = dto::json_get<std::string>(json, "name").value();
|
||||||
|
|
||||||
|
std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
|
||||||
|
|
||||||
auto new_node = create_node(name, user, file, std::make_optional(parent));
|
auto new_node = create_node(name, user, file, std::make_optional(parent));
|
||||||
if (std::holds_alternative<db::INode>(new_node))
|
if (std::holds_alternative<db::INode>(new_node))
|
||||||
cbk(dto::Responses::get_new_node_res(std::get<db::INode>(new_node).getValueOfId()));
|
cbk(dto::Responses::get_new_node_res(std::get<db::INode>(new_node).getValueOfId()));
|
||||||
@ -362,6 +116,7 @@ namespace api {
|
|||||||
|
|
||||||
void fs::delete_node_req(req_type req, cbk_type cbk, uint64_t node) {
|
void fs::delete_node_req(req_type req, cbk_type cbk, uint64_t node) {
|
||||||
db::User user = dto::get_user(req);
|
db::User user = dto::get_user(req);
|
||||||
|
std::unique_lock lock(*get_user_mutex(user.getValueOfId()));
|
||||||
auto inode = get_node_and_validate(user, node);
|
auto inode = get_node_and_validate(user, node);
|
||||||
if (!inode.has_value())
|
if (!inode.has_value())
|
||||||
cbk(dto::Responses::get_badreq_res("Unknown node"));
|
cbk(dto::Responses::get_badreq_res("Unknown node"));
|
||||||
@ -370,7 +125,8 @@ namespace api {
|
|||||||
else {
|
else {
|
||||||
auto chan = std::make_shared<msd::channel<std::string>>();
|
auto chan = std::make_shared<msd::channel<std::string>>();
|
||||||
std::string("Waiting in queue...\n") >> (*chan);
|
std::string("Waiting in queue...\n") >> (*chan);
|
||||||
get_delete_loop()->queueInLoop([chan, inode=*inode]{
|
get_delete_loop()->queueInLoop([chan, inode=*inode, user=user.getValueOfId()]{
|
||||||
|
std::unique_lock lock(*get_user_mutex(user));
|
||||||
delete_node(inode, *chan);
|
delete_node(inode, *chan);
|
||||||
chan->close();
|
chan->close();
|
||||||
});
|
});
|
||||||
@ -391,6 +147,8 @@ namespace api {
|
|||||||
constexpr int image_height = 256;
|
constexpr int image_height = 256;
|
||||||
db::User user = dto::get_user(req);
|
db::User user = dto::get_user(req);
|
||||||
|
|
||||||
|
std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
|
||||||
|
|
||||||
auto inode = get_node_and_validate(user, node);
|
auto inode = get_node_and_validate(user, node);
|
||||||
if (!inode.has_value())
|
if (!inode.has_value())
|
||||||
return cbk(dto::Responses::get_badreq_res("Unknown node"));
|
return cbk(dto::Responses::get_badreq_res("Unknown node"));
|
||||||
@ -435,6 +193,9 @@ namespace api {
|
|||||||
|
|
||||||
void fs::create_zip(req_type req, cbk_type cbk) {
|
void fs::create_zip(req_type req, cbk_type cbk) {
|
||||||
db::User user = dto::get_user(req);
|
db::User user = dto::get_user(req);
|
||||||
|
|
||||||
|
std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
|
||||||
|
|
||||||
Json::Value& json = *req->jsonObject();
|
Json::Value& json = *req->jsonObject();
|
||||||
try {
|
try {
|
||||||
if (!json.isMember("nodes")) throw std::exception();
|
if (!json.isMember("nodes")) throw std::exception();
|
||||||
@ -456,12 +217,14 @@ namespace api {
|
|||||||
auto progress = in_progress_zips.at(key);
|
auto progress = in_progress_zips.at(key);
|
||||||
return cbk(dto::Responses::get_create_zip_done_res(std::get<1>(progress), std::get<2>(progress)));
|
return cbk(dto::Responses::get_create_zip_done_res(std::get<1>(progress), std::get<2>(progress)));
|
||||||
}
|
}
|
||||||
|
std::string file_name = "./temp/fs_" + std::to_string(next_temp_id++) + ".zip";
|
||||||
|
in_progress_zips.emplace(key, std::make_tuple(file_name, 0, 1));
|
||||||
|
get_zip_loop()->queueInLoop([key = std::move(key), nodes = std::move(nodes), file_name = std::move(file_name), user=user.getValueOfId()]{
|
||||||
|
{
|
||||||
|
std::shared_lock lock(*get_user_mutex(user));
|
||||||
uint64_t size = 0;
|
uint64_t size = 0;
|
||||||
for (const auto& node : nodes) size += calc_total_size(node);
|
for (const auto& node : nodes) size += calc_total_size(node);
|
||||||
std::string file_name = "./temp/fs_" + std::to_string(next_temp_id++) + ".zip";
|
std::get<2>(in_progress_zips.at(key)) = size;
|
||||||
in_progress_zips.emplace(key, std::make_tuple(file_name, 0, size));
|
|
||||||
get_zip_loop()->queueInLoop([key = std::move(key), nodes = std::move(nodes), file_name = std::move(file_name)]{
|
|
||||||
{
|
|
||||||
struct zip_t* zip = zip_open(file_name.c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
|
struct zip_t* zip = zip_open(file_name.c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
|
||||||
for (const db::INode& node : nodes)
|
for (const db::INode& node : nodes)
|
||||||
add_to_zip(zip, key, node, "");
|
add_to_zip(zip, key, node, "");
|
||||||
@ -470,7 +233,7 @@ namespace api {
|
|||||||
zip_to_temp_map.emplace(key, file_name);
|
zip_to_temp_map.emplace(key, file_name);
|
||||||
in_progress_zips.erase(key);
|
in_progress_zips.erase(key);
|
||||||
});
|
});
|
||||||
return cbk(dto::Responses::get_create_zip_done_res(0, size));
|
return cbk(dto::Responses::get_create_zip_done_res(0, 1));
|
||||||
} catch (const std::exception&) {
|
} catch (const std::exception&) {
|
||||||
cbk(dto::Responses::get_badreq_res("Validation error"));
|
cbk(dto::Responses::get_badreq_res("Validation error"));
|
||||||
}
|
}
|
||||||
@ -479,6 +242,8 @@ namespace api {
|
|||||||
void fs::download(req_type req, cbk_type cbk) {
|
void fs::download(req_type req, cbk_type cbk) {
|
||||||
db::User user = dto::get_user(req);
|
db::User user = dto::get_user(req);
|
||||||
|
|
||||||
|
std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
|
||||||
|
|
||||||
auto node_id = req->getOptionalParameter<uint64_t>("id");
|
auto node_id = req->getOptionalParameter<uint64_t>("id");
|
||||||
if (!node_id.has_value()) {
|
if (!node_id.has_value()) {
|
||||||
cbk(dto::Responses::get_badreq_res("Invalid node"));
|
cbk(dto::Responses::get_badreq_res("Invalid node"));
|
||||||
@ -516,6 +281,8 @@ namespace api {
|
|||||||
void fs::download_multi(req_type req, cbk_type cbk) {
|
void fs::download_multi(req_type req, cbk_type cbk) {
|
||||||
db::User user = dto::get_user(req);
|
db::User user = dto::get_user(req);
|
||||||
|
|
||||||
|
std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
|
||||||
|
|
||||||
auto node_ids_str = req->getOptionalParameter<std::string>("id");
|
auto node_ids_str = req->getOptionalParameter<std::string>("id");
|
||||||
if (!node_ids_str.has_value())
|
if (!node_ids_str.has_value())
|
||||||
return cbk(dto::Responses::get_badreq_res("No nodes"));
|
return cbk(dto::Responses::get_badreq_res("No nodes"));
|
||||||
@ -540,6 +307,8 @@ namespace api {
|
|||||||
void fs::download_preview(req_type req, cbk_type cbk, uint64_t node) {
|
void fs::download_preview(req_type req, cbk_type cbk, uint64_t node) {
|
||||||
db::User user = dto::get_user(req);
|
db::User user = dto::get_user(req);
|
||||||
|
|
||||||
|
std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
|
||||||
|
|
||||||
auto inode = get_node_and_validate(user, node);
|
auto inode = get_node_and_validate(user, node);
|
||||||
if (!inode.has_value())
|
if (!inode.has_value())
|
||||||
return cbk(dto::Responses::get_badreq_res("Unknown node"));
|
return cbk(dto::Responses::get_badreq_res("Unknown node"));
|
||||||
@ -557,6 +326,8 @@ namespace api {
|
|||||||
void fs::get_type(req_type req, cbk_type cbk, uint64_t node){
|
void fs::get_type(req_type req, cbk_type cbk, uint64_t node){
|
||||||
db::User user = dto::get_user(req);
|
db::User user = dto::get_user(req);
|
||||||
|
|
||||||
|
std::shared_lock lock(*get_user_mutex(user.getValueOfId()));
|
||||||
|
|
||||||
auto inode = get_node_and_validate(user, node);
|
auto inode = get_node_and_validate(user, node);
|
||||||
if (!inode.has_value())
|
if (!inode.has_value())
|
||||||
return cbk(dto::Responses::get_badreq_res("Unknown node"));
|
return cbk(dto::Responses::get_badreq_res("Unknown node"));
|
||||||
@ -572,4 +343,3 @@ namespace api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma clang diagnostic pop
|
|
@ -21,6 +21,9 @@ namespace api {
|
|||||||
msd::channel<std::string> chan;
|
msd::channel<std::string> chan;
|
||||||
db::User user = dto::get_user(req);
|
db::User user = dto::get_user(req);
|
||||||
auth::revoke_all(user);
|
auth::revoke_all(user);
|
||||||
|
|
||||||
|
std::unique_lock lock(*fs::get_user_mutex(user.getValueOfId()));
|
||||||
|
|
||||||
fs::delete_node((fs::get_node(user.getValueOfRootId())).value(), chan, true);
|
fs::delete_node((fs::get_node(user.getValueOfRootId())).value(), chan, true);
|
||||||
user_mapper.deleteOne(user);
|
user_mapper.deleteOne(user);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user