Handle permission denied errors.
This commit is contained in:
+17
-2
@@ -207,7 +207,13 @@ std::vector<DbFileInfo> AudioDirectoryInfoImpl::QueryTracks()
|
||||
std::vector<AudioFileMetadata> AudioDirectoryInfoImpl::GetFiles()
|
||||
{
|
||||
OpenAudioDb();
|
||||
std::vector<DbFileInfo> dbFiles = UpdateDbFiles();
|
||||
std::vector<DbFileInfo> dbFiles;
|
||||
try {
|
||||
dbFiles = UpdateDbFiles();
|
||||
} catch (const std::exception &e) {
|
||||
Lv2Log::error("Error updating audio file info: %s - %s",
|
||||
path.string().c_str(), e.what());
|
||||
}
|
||||
std::vector<AudioFileMetadata> metadataResults;
|
||||
for (const auto &dbFile : dbFiles)
|
||||
{
|
||||
@@ -298,7 +304,16 @@ std::vector<DbFileInfo> AudioDirectoryInfoImpl::UpdateDbFiles()
|
||||
|
||||
for (auto dirEntry : fs::directory_iterator(path))
|
||||
{
|
||||
if (!dirEntry.is_directory())
|
||||
bool isDirectory;
|
||||
try {
|
||||
isDirectory = dirEntry.is_directory();
|
||||
} catch (
|
||||
const std::filesystem::filesystem_error &e) {
|
||||
Lv2Log::error("Error accessing directory entry: %s - %s", dirEntry.path().string().c_str(), e.what());
|
||||
continue; // skip this entry if we cannot access it.
|
||||
}
|
||||
|
||||
if (!isDirectory)
|
||||
{
|
||||
auto path = dirEntry.path();
|
||||
std::string name = path.filename();
|
||||
|
||||
+51
-44
@@ -142,25 +142,27 @@ uint32_t FileMetadataFeature::getFileMetadata(
|
||||
json_variant jsonData;
|
||||
std::string result;
|
||||
|
||||
if (fs::exists(path))
|
||||
try
|
||||
{
|
||||
try
|
||||
if (fs::exists(path))
|
||||
{
|
||||
std::ifstream f(path);
|
||||
json_reader reader(f);
|
||||
reader.read(&jsonData);
|
||||
result = jsonData.as_object()->at(std::string(key)).as_string();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
else
|
||||
{
|
||||
Lv2Log::debug(SS("No metadata file found for " << absolute_path << " key: " << key));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::debug(SS("No metadata file found for " << absolute_path << " key: " << key));
|
||||
Lv2Log::debug(SS("Permission denied for " << absolute_path << " key: " << key));
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t len = result.length() + 1; // +1 for null terminator
|
||||
if (len > fileMetadataSize || fileMetadata == nullptr)
|
||||
{
|
||||
@@ -168,7 +170,7 @@ uint32_t FileMetadataFeature::getFileMetadata(
|
||||
return len;
|
||||
}
|
||||
memcpy(fileMetadata, result.c_str(), len);
|
||||
Lv2Log::debug(SS("Get file metadata for " << absolute_path << " key: " << key << " value: " << result));
|
||||
Lv2Log::debug(SS("Get file metadata for " << absolute_path << " key: " << key << " value: " << result));
|
||||
return len;
|
||||
}
|
||||
PIPEDAL_FileMetadata_Status FileMetadataFeature::deleteFileMetadata(
|
||||
@@ -181,52 +183,57 @@ PIPEDAL_FileMetadata_Status FileMetadataFeature::deleteFileMetadata(
|
||||
}
|
||||
Lv2Log::debug(SS("Delete file metadata for " << absolute_path << " key: " << key));
|
||||
|
||||
std::filesystem::path path{SS(absolute_path << ".mdata")};
|
||||
try {
|
||||
std::filesystem::path path{SS(absolute_path << ".mdata")};
|
||||
|
||||
if (!fs::exists(path))
|
||||
{
|
||||
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Metadata file does not exist
|
||||
}
|
||||
if (!fs::exists(path))
|
||||
{
|
||||
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Metadata file does not exist
|
||||
}
|
||||
|
||||
json_variant jsonData;
|
||||
try
|
||||
{
|
||||
std::ifstream f(path);
|
||||
json_reader reader(f);
|
||||
reader.read(&jsonData);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Failed to read existing metadata
|
||||
}
|
||||
json_variant jsonData;
|
||||
try
|
||||
{
|
||||
std::ifstream f(path);
|
||||
json_reader reader(f);
|
||||
reader.read(&jsonData);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Failed to read existing metadata
|
||||
}
|
||||
|
||||
auto obj = jsonData.as_object();
|
||||
auto it = obj->find(std::string(key));
|
||||
if (it == obj->end())
|
||||
{
|
||||
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Key not found
|
||||
}
|
||||
auto obj = jsonData.as_object();
|
||||
auto it = obj->find(std::string(key));
|
||||
if (it == obj->end())
|
||||
{
|
||||
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Key not found
|
||||
}
|
||||
|
||||
obj->erase(it);
|
||||
obj->erase(it);
|
||||
|
||||
if (obj->begin() == obj->end())
|
||||
{
|
||||
// If the object is empty, delete the metadata file
|
||||
fs::remove(path);
|
||||
return PIPEDAL_FILE_METADATA_SUCCESS; // Metadata deleted successfully
|
||||
}
|
||||
if (obj->begin() == obj->end())
|
||||
{
|
||||
// If the object is empty, delete the metadata file
|
||||
fs::remove(path);
|
||||
return PIPEDAL_FILE_METADATA_SUCCESS; // Metadata deleted successfully
|
||||
}
|
||||
|
||||
std::ofstream f(path);
|
||||
if (!f.is_open())
|
||||
{
|
||||
Lv2Log::error(SS("Failed to write metadata file " << path));
|
||||
std::ofstream f(path);
|
||||
if (!f.is_open())
|
||||
{
|
||||
Lv2Log::error(SS("Failed to write metadata file " << path));
|
||||
return PIPEDAL_FILE_METADATA_PERMISSION_DENIED; // Failed to open existing metadata
|
||||
}
|
||||
|
||||
json_writer writer(f);
|
||||
writer.write(jsonData);
|
||||
|
||||
return PIPEDAL_FILE_METADATA_SUCCESS;
|
||||
} catch (const std::exception &e) {
|
||||
Lv2Log::error(SS("Exception while deleting metadata: " << e.what()));
|
||||
return PIPEDAL_FILE_METADATA_PERMISSION_DENIED; // Failed to open existing metadata
|
||||
}
|
||||
|
||||
json_writer writer(f);
|
||||
writer.write(jsonData);
|
||||
|
||||
return PIPEDAL_FILE_METADATA_SUCCESS;
|
||||
}
|
||||
|
||||
PIPEDAL_FileMetadata_Status FileMetadataFeature::S_setFileMetadata(
|
||||
|
||||
+76
-50
@@ -1690,7 +1690,8 @@ static void AddFilesToResult(
|
||||
}
|
||||
catch (const std::exception &error)
|
||||
{
|
||||
throw std::logic_error("GetFileList failed. Directory not found: " + rootPath.string());
|
||||
throw std::logic_error(
|
||||
SS("GetFileList failed. " << rootPath.string() << " - " << error.what()));
|
||||
}
|
||||
|
||||
// sort lexicographically
|
||||
@@ -1723,7 +1724,7 @@ static void AddTracksToResult(
|
||||
std::set<std::string> validExtensions = fileProperty.GetPermittedFileExtensions(
|
||||
modDirectoryInfo ? modDirectoryInfo->modType : "");
|
||||
|
||||
if (validExtensions.size() == 0)
|
||||
if (validExtensions.size() == 0)
|
||||
{
|
||||
const auto &audioExtensions = MimeTypes::instance().AudioExtensions();
|
||||
validExtensions.insert(audioExtensions.begin(), audioExtensions.end());
|
||||
@@ -1733,54 +1734,76 @@ static void AddTracksToResult(
|
||||
try
|
||||
{
|
||||
// Add directories first.
|
||||
for (auto const &dir_entry : std::filesystem::directory_iterator(rootPath))
|
||||
try
|
||||
{
|
||||
if (!IsValidUtf8(dir_entry.path().string()))
|
||||
for (auto const &dir_entry : std::filesystem::directory_iterator(rootPath))
|
||||
{
|
||||
Lv2Log::warning("Invalid UTF-8 name in directory: " + dir_entry.path().string());
|
||||
continue; // skip invalid UTF-8 names.
|
||||
}
|
||||
const auto &path = dir_entry.path();
|
||||
auto name = path.filename().string();
|
||||
if (dir_entry.is_directory())
|
||||
{
|
||||
resultFiles.push_back(FileEntry{path, name, true, fs::is_symlink(path)});
|
||||
if (!IsValidUtf8(dir_entry.path().string()))
|
||||
{
|
||||
Lv2Log::warning("Invalid UTF-8 name in directory: " + dir_entry.path().string());
|
||||
continue; // skip invalid UTF-8 names.
|
||||
}
|
||||
const auto &path = dir_entry.path();
|
||||
auto name = path.filename().string();
|
||||
try {
|
||||
if (dir_entry.is_directory())
|
||||
{
|
||||
resultFiles.push_back(FileEntry{path, name, true, dir_entry.is_symlink()});
|
||||
}
|
||||
} catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to add directory entry: " << path.string() << " - " << e.what()));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception &error)
|
||||
{
|
||||
throw std::logic_error(
|
||||
SS("AddTracksToResult failed to enumerate directories. " << rootPath.string() << " - " << error.what()));
|
||||
}
|
||||
auto collator = Locale::GetInstance()->GetCollator();
|
||||
std::sort(
|
||||
resultFiles.begin(),
|
||||
resultFiles.begin(),
|
||||
resultFiles.end(),
|
||||
[collator](const FileEntry &l, const FileEntry &r)
|
||||
{
|
||||
if (l.isDirectory_ != r.isDirectory_)
|
||||
{
|
||||
return l.isDirectory_ > r.isDirectory_;
|
||||
}
|
||||
return collator->Compare(l.displayName_ ,r.displayName_) < 0;
|
||||
});
|
||||
if (l.isDirectory_ != r.isDirectory_)
|
||||
{
|
||||
return l.isDirectory_ > r.isDirectory_;
|
||||
}
|
||||
return collator->Compare(l.displayName_, r.displayName_) < 0;
|
||||
});
|
||||
// Add audio files.
|
||||
auto audioFiles = AudioDirectoryInfo::Create(rootPath,
|
||||
GetShadowIndexDirectory(audioRootDirectory,rootPath)
|
||||
);
|
||||
for (const auto &audioFile : audioFiles->GetFiles())
|
||||
GetShadowIndexDirectory(audioRootDirectory, rootPath));
|
||||
|
||||
try
|
||||
{
|
||||
fs::path audioFilePath = rootPath / audioFile.fileName();
|
||||
std::string extension = UiFileProperty::GetFileExtension(audioFilePath);
|
||||
if (validExtensions.size() == 0 || validExtensions.contains(extension) || validExtensions.contains(".*"))
|
||||
for (const auto &audioFile : audioFiles->GetFiles())
|
||||
{
|
||||
resultFiles.push_back(
|
||||
FileEntry(
|
||||
audioFilePath,
|
||||
audioFile.title(),
|
||||
false,
|
||||
std::make_shared<AudioFileMetadata>(audioFile)));
|
||||
fs::path audioFilePath = rootPath / audioFile.fileName();
|
||||
std::string extension = UiFileProperty::GetFileExtension(audioFilePath);
|
||||
if (validExtensions.size() == 0 || validExtensions.contains(extension) || validExtensions.contains(".*"))
|
||||
{
|
||||
resultFiles.push_back(
|
||||
FileEntry(
|
||||
audioFilePath,
|
||||
audioFile.title(),
|
||||
false,
|
||||
std::make_shared<AudioFileMetadata>(audioFile)));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception &error)
|
||||
{
|
||||
throw std::logic_error(
|
||||
SS("AddTracksToResult failed to enumerate audio files. " << rootPath.string() << " - " << error.what()));
|
||||
}
|
||||
|
||||
}
|
||||
catch (const std::exception &error)
|
||||
{
|
||||
throw std::logic_error("GetFileList failed. Directory not found: " + rootPath.string());
|
||||
throw std::logic_error(SS("GetFileList failed. " << error.what() << "(" << rootPath.string() << ")"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1869,7 +1892,7 @@ FileRequestResult Storage::GetModFileList2(const std::string &relativePath, cons
|
||||
|
||||
if (IsInAudioTracksDirectory(relativePath))
|
||||
{
|
||||
AddTracksToResult(this->GetPluginUploadDirectory(),result, rootModDirectory, fileProperty, relativePath);
|
||||
AddTracksToResult(this->GetPluginUploadDirectory(), result, rootModDirectory, fileProperty, relativePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2046,7 +2069,7 @@ void Storage::DeleteSampleFile(const std::filesystem::path &fileName)
|
||||
else
|
||||
{
|
||||
std::filesystem::remove(fileName);
|
||||
if (IsInAudioTracksDirectory(fileName))
|
||||
if (IsInAudioTracksDirectory(fileName))
|
||||
{
|
||||
// remove the metadata file as well.
|
||||
std::filesystem::remove(fileName.string() + ".mdata");
|
||||
@@ -2074,13 +2097,12 @@ std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, co
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool Storage::IsValidArtworkFile(const std::filesystem::path& fullPath)
|
||||
bool Storage::IsValidArtworkFile(const std::filesystem::path &fullPath)
|
||||
{
|
||||
if (IsInAudioTracksDirectory(fullPath) && isArtworkFileName(fullPath.filename().string()))
|
||||
{
|
||||
// allow artwork files.
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -2208,9 +2230,10 @@ std::string Storage::RenameFilePropertyFile(
|
||||
}
|
||||
|
||||
std::filesystem::rename(oldPath, newPath);
|
||||
if (fs::exists(oldPath.string()+".mdata")) {
|
||||
if (fs::exists(oldPath.string() + ".mdata"))
|
||||
{
|
||||
// rename the metadata file as well.
|
||||
std::filesystem::rename(oldPath.string()+".mdata", newPath.string()+".mdata");
|
||||
std::filesystem::rename(oldPath.string() + ".mdata", newPath.string() + ".mdata");
|
||||
}
|
||||
return newPath;
|
||||
}
|
||||
@@ -2222,12 +2245,13 @@ fs::path MakeVersionedPath(const fs::path &path)
|
||||
return path; // no need to version a non-existing file.
|
||||
}
|
||||
fs::path newPath = path;
|
||||
auto stem = newPath.stem().string();;
|
||||
auto stem = newPath.stem().string();
|
||||
;
|
||||
if (stem.ends_with(")"))
|
||||
{
|
||||
// remove the trailing (n) from the file name.
|
||||
size_t pos = stem.find_last_of('(');
|
||||
std::string stemVersion = stem.substr(pos+1, stem.length()-1-(pos+1));
|
||||
std::string stemVersion = stem.substr(pos + 1, stem.length() - 1 - (pos + 1));
|
||||
// check if it is a number.
|
||||
if (stemVersion.find_first_not_of("0123456789") == std::string::npos)
|
||||
{
|
||||
@@ -2237,7 +2261,7 @@ fs::path MakeVersionedPath(const fs::path &path)
|
||||
{
|
||||
// remove trailing space.
|
||||
stem = stem.substr(0, stem.length() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2246,12 +2270,15 @@ fs::path MakeVersionedPath(const fs::path &path)
|
||||
{
|
||||
// append (n) to the file name.
|
||||
std::string newFileName;
|
||||
if (version == 0) {
|
||||
if (version == 0)
|
||||
{
|
||||
newFileName = SS(stem << newPath.extension().string());
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// append (n) to the file name.
|
||||
newFileName = SS(stem << " (" << std::to_string(version) << ")" << newPath.extension().string());
|
||||
}
|
||||
}
|
||||
newPath = newPath.parent_path() / newFileName;
|
||||
++version;
|
||||
}
|
||||
@@ -2280,7 +2307,6 @@ std::string Storage::CopyFilePropertyFile(
|
||||
|
||||
std::filesystem::path newPath = this->GetPluginUploadDirectory() / uiFileProperty.directory() / newRelativePath;
|
||||
|
||||
|
||||
if (!this->IsValidSampleFileName(newPath))
|
||||
{
|
||||
throw std::runtime_error("Invalid file name.");
|
||||
@@ -2299,14 +2325,14 @@ std::string Storage::CopyFilePropertyFile(
|
||||
|
||||
std::filesystem::create_hard_link(oldPath, newPath);
|
||||
|
||||
if (IsInAudioTracksDirectory(oldPath)
|
||||
&& IsInAudioTracksDirectory(newPath))
|
||||
if (IsInAudioTracksDirectory(oldPath) && IsInAudioTracksDirectory(newPath))
|
||||
{
|
||||
std::filesystem::path metadataPath = SS(oldPath.string() << ".mdata");
|
||||
if (fs::exists(metadataPath)) {
|
||||
if (fs::exists(metadataPath))
|
||||
{
|
||||
// copy the metadata file as well.
|
||||
std::filesystem::copy_file(
|
||||
metadataPath,
|
||||
metadataPath,
|
||||
SS(newPath.string() << ".mdata"),
|
||||
fs::copy_options::overwrite_existing);
|
||||
}
|
||||
|
||||
+81
-76
@@ -52,15 +52,13 @@ static const std::string PLUGIN_PRESETS_MIME_TYPE = "application/vnd.pipedal.plu
|
||||
static const std::string PRESET_MIME_TYPE = "application/vnd.pipedal.preset";
|
||||
static const std::string BANK_MIME_TYPE = "application/vnd.pipedal.bank";
|
||||
|
||||
static const std::string CACHE_CONTROL_INDEFINITELY = "max-age=31536000,public,immutable"; // 1 year
|
||||
static const std::string CACHE_CONTROL_SHORT = "max-age=300,public"; // 5 minutes
|
||||
|
||||
static const std::string CACHE_CONTROL_INDEFINITELY = "max-age=31536000,public,immutable"; // 1 year
|
||||
static const std::string CACHE_CONTROL_SHORT = "max-age=300,public"; // 5 minutes
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace boost::system;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
class UserUploadResponse
|
||||
{
|
||||
public:
|
||||
@@ -87,7 +85,7 @@ int32_t ConvertThumbnailSize(const std::string ¶m)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return static_cast<int32_t > (std::stoi(param));
|
||||
return static_cast<int32_t>(std::stoi(param));
|
||||
}
|
||||
|
||||
static bool IsZipFile(const std::filesystem::path &path)
|
||||
@@ -128,7 +126,6 @@ private:
|
||||
std::vector<std::string> extensions;
|
||||
};
|
||||
|
||||
|
||||
class DownloadIntercept : public RequestHandler
|
||||
{
|
||||
PiPedalModel *model;
|
||||
@@ -392,16 +389,15 @@ public:
|
||||
{
|
||||
auto lastModified = std::filesystem::last_write_time(path);
|
||||
res.set(HttpField::LastModified, HtmlHelper::timeToHttpDate(lastModified));
|
||||
}
|
||||
AudioDirectoryInfo::Ptr CreateDirectoryInfo(const fs::path &path) {
|
||||
}
|
||||
AudioDirectoryInfo::Ptr CreateDirectoryInfo(const fs::path &path)
|
||||
{
|
||||
return AudioDirectoryInfo::Create(path,
|
||||
GetShadowIndexDirectory(
|
||||
this->model->GetPluginUploadDirectory(),
|
||||
path));
|
||||
GetShadowIndexDirectory(
|
||||
this->model->GetPluginUploadDirectory(),
|
||||
path));
|
||||
}
|
||||
|
||||
|
||||
|
||||
virtual void get_response(
|
||||
const uri &request_uri,
|
||||
HttpRequest &req,
|
||||
@@ -500,89 +496,98 @@ public:
|
||||
throw PiPedalException("File not found.");
|
||||
}
|
||||
AudioDirectoryInfo::Ptr audioDirectory = CreateDirectoryInfo(path.parent_path());
|
||||
auto files = audioDirectory->GetFiles();
|
||||
for (const auto &file : files)
|
||||
try
|
||||
{
|
||||
std::string fileNameOnly = path.filename();
|
||||
if (file.fileName() == fileNameOnly)
|
||||
auto files = audioDirectory->GetFiles();
|
||||
for (const auto &file : files)
|
||||
{
|
||||
std::stringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(file);
|
||||
res.setBody(ss.str());
|
||||
return;
|
||||
std::string fileNameOnly = path.filename();
|
||||
if (file.fileName() == fileNameOnly)
|
||||
{
|
||||
std::stringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(file);
|
||||
res.setBody(ss.str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error("Error getting audio directory info: %s - (%s)", path.c_str(), e.what());
|
||||
throw e;
|
||||
}
|
||||
// If we get here, the file was not found in the directory.
|
||||
throw PiPedalException("File not found in directory.");
|
||||
}
|
||||
else if (segment == "Thumbnail")
|
||||
{
|
||||
ThumbnailTemporaryFile thumbnailTemporaryFile;
|
||||
try
|
||||
{
|
||||
fs::path path = request_uri.query("path");
|
||||
if (path.empty() ||
|
||||
!fs::exists(path) ||
|
||||
!this->model->IsInUploadsDirectory(path) ||
|
||||
HasDotDot(path))
|
||||
std::shared_ptr<TemporaryFile> thumbnail;
|
||||
try
|
||||
{
|
||||
// path for folder thumbnails.
|
||||
path = request_uri.query("ffile");
|
||||
|
||||
std::shared_ptr<TemporaryFile> thumbnail;
|
||||
|
||||
if (!path.empty() && fs::exists(path) &&
|
||||
this->model->IsInUploadsDirectory(path) &&
|
||||
!HasDotDot(path))
|
||||
fs::path path = request_uri.query("path");
|
||||
if (path.empty() ||
|
||||
!fs::exists(path) ||
|
||||
!this->model->IsInUploadsDirectory(path) ||
|
||||
HasDotDot(path))
|
||||
{
|
||||
// path is a folder.
|
||||
thumbnail = std::make_shared<TemporaryFile>();
|
||||
thumbnail->SetNonDeletedPath(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto t = AudioDirectoryInfo::DefaultThumbnailTemporaryFile();
|
||||
thumbnail = std::make_shared<TemporaryFile>();
|
||||
thumbnail->SetNonDeletedPath(t.Path());
|
||||
|
||||
std::shared_ptr<TemporaryFile> thumbnail;
|
||||
// path for folder thumbnails.
|
||||
path = request_uri.query("ffile");
|
||||
|
||||
if (!path.empty() && fs::exists(path) &&
|
||||
this->model->IsInUploadsDirectory(path) &&
|
||||
!HasDotDot(path))
|
||||
{
|
||||
// path is a folder.
|
||||
thumbnail = std::make_shared<TemporaryFile>();
|
||||
thumbnail->SetNonDeletedPath(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto t = AudioDirectoryInfo::DefaultThumbnailTemporaryFile();
|
||||
thumbnail = std::make_shared<TemporaryFile>();
|
||||
thumbnail->SetNonDeletedPath(t.Path());
|
||||
}
|
||||
|
||||
res.set(HttpField::content_type, MimeTypes::instance().MimeTypeFromExtension(path.extension()));
|
||||
res.set(HttpField::cache_control, CACHE_CONTROL_INDEFINITELY); // URL is cache-busted, and will change if the file ismodified.
|
||||
setLastModifiedFromFile(res, thumbnail->Path());
|
||||
res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnail->Path())));
|
||||
res.setBodyFile(thumbnail);
|
||||
return;
|
||||
}
|
||||
|
||||
res.set(HttpField::content_type, MimeTypes::instance().MimeTypeFromExtension(path.extension()));
|
||||
res.set(HttpField::cache_control, CACHE_CONTROL_INDEFINITELY); // URL is cache-busted, and will change if the file ismodified.
|
||||
setLastModifiedFromFile(res,thumbnail->Path());
|
||||
res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnail->Path())));
|
||||
res.setBodyFile(thumbnail);
|
||||
return;
|
||||
int32_t width = ConvertThumbnailSize(request_uri.query("w"));
|
||||
int32_t height = ConvertThumbnailSize(request_uri.query("h"));
|
||||
|
||||
AudioDirectoryInfo::Ptr audioDirectory = CreateDirectoryInfo(
|
||||
path.parent_path());
|
||||
audioDirectory->GetFiles(); // ensure that the .index file is up to date.
|
||||
|
||||
thumbnailTemporaryFile = audioDirectory->GetThumbnail(path.filename(), width, height);
|
||||
}
|
||||
|
||||
|
||||
int32_t width = ConvertThumbnailSize(request_uri.query("w"));
|
||||
int32_t height = ConvertThumbnailSize(request_uri.query("h"));
|
||||
|
||||
AudioDirectoryInfo::Ptr audioDirectory = CreateDirectoryInfo(
|
||||
path.parent_path());
|
||||
audioDirectory->GetFiles(); // ensure that the .index file is up to date.
|
||||
|
||||
ThumbnailTemporaryFile thumbnail;
|
||||
try {
|
||||
thumbnail = audioDirectory->GetThumbnail(path.filename(), width, height);
|
||||
} catch (const std::exception &e) {
|
||||
thumbnail = audioDirectory->DefaultThumbnailTemporaryFile();
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
fs::path defaultThumbnail = model->GetWebRoot() / "img/missing_thumbnail.jpg";
|
||||
thumbnailTemporaryFile.SetNonDeletedPath(defaultThumbnail, "image/jpeg");
|
||||
}
|
||||
res.set(HttpField::content_type, thumbnail.GetMimeType());
|
||||
res.set(HttpField::content_type, thumbnailTemporaryFile.GetMimeType());
|
||||
|
||||
res.set(HttpField::cache_control, CACHE_CONTROL_INDEFINITELY); // URL is cache-busted with time-stamp.
|
||||
setLastModifiedFromFile(res, path);
|
||||
res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnail.Path())));
|
||||
setLastModifiedFromFile(res, thumbnailTemporaryFile.Path());
|
||||
res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnailTemporaryFile.Path())));
|
||||
|
||||
std::filesystem::path t = thumbnail.Path();
|
||||
res.setBodyFile(t,thumbnail.DeleteFile());
|
||||
thumbnail.Detach();
|
||||
std::filesystem::path t = thumbnailTemporaryFile.Path();
|
||||
res.setBodyFile(t, thumbnailTemporaryFile.DeleteFile());
|
||||
thumbnailTemporaryFile.Detach();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error("Error getting thumbnail: %s - (%s)", request_uri.str().c_str(), e.what());
|
||||
Lv2Log::error("Error getting thumbnail: %s - (%s)", request_uri.str().c_str(), e.what());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -605,11 +610,11 @@ public:
|
||||
{
|
||||
result = (directoryPath / result).string();
|
||||
}
|
||||
|
||||
|
||||
// json-encode the result.
|
||||
std::stringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(result);
|
||||
writer.write(result);
|
||||
|
||||
res.setBody(ss.str());
|
||||
}
|
||||
@@ -637,11 +642,11 @@ public:
|
||||
{
|
||||
result = (directoryPath / result).string();
|
||||
}
|
||||
|
||||
|
||||
// json-encode the result.
|
||||
std::stringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(result);
|
||||
writer.write(result);
|
||||
|
||||
res.setBody(ss.str());
|
||||
}
|
||||
|
||||
@@ -105,7 +105,6 @@ add_custom_command(
|
||||
public/var/current_pedalboard.json
|
||||
public/var/config.json
|
||||
public/favicon.ico
|
||||
public/index.html
|
||||
public/iso_codes.json
|
||||
public/logo512.png
|
||||
public/sample_lv2_plugins.json
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-visual" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="color-scheme" content="dark light"/> <!-- uses media queries in web views. -->
|
||||
<meta name="description" content="PiPedal Guitar Stomp Box for Raspberry Pi" />
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="color-scheme" content="dark light"/> <!-- uses media queries in web views. -->
|
||||
<meta name="description" content="PiPedal Guitar Pedals" />
|
||||
<!--
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
|
||||
-->
|
||||
<link rel="stylesheet" href="/css/roboto.css" />
|
||||
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>PiPedal</title>
|
||||
<style>
|
||||
BODY {
|
||||
background: #D0D0D0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style id="bgStyle">
|
||||
BODY {
|
||||
background: #333; overscroll-behavior: "none"
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
const androidHosted = !!(window.AndroidHost);
|
||||
|
||||
var colorScheme = localStorage.getItem("colorScheme");
|
||||
if (androidHosted)
|
||||
{
|
||||
var hostColorScheme = window.AndroidHost.getThemePreference();
|
||||
switch (hostColorScheme)
|
||||
{
|
||||
case 0:
|
||||
colorScheme = "Light";
|
||||
break;
|
||||
case 1:
|
||||
colorScheme = "Dark";
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
// use the host's interpretation of the current system night mode.
|
||||
colorScheme = window.AndroidHost.isDarkTheme() ? "Dark": "Light";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!colorScheme)
|
||||
{
|
||||
colorScheme = "Dark";
|
||||
}
|
||||
var darkMode = false;
|
||||
var useSystem = false;
|
||||
|
||||
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
switch (colorScheme) {
|
||||
case null:
|
||||
default:
|
||||
darkMode = false;
|
||||
break;
|
||||
|
||||
case "Light":
|
||||
darkMode = false;
|
||||
break;
|
||||
case "Dark":
|
||||
darkMode = true;
|
||||
break;
|
||||
case "System":
|
||||
useSystem = true;
|
||||
break;
|
||||
}
|
||||
if (useSystem) {
|
||||
darkMode = prefersDark;
|
||||
}
|
||||
|
||||
if (!darkMode) {
|
||||
let bgStyle = document.getElementById("bgStyle");
|
||||
if (bgStyle) {
|
||||
// disable the style block.
|
||||
bgStyle.setAttribute('media', "max-width: 1px");
|
||||
}
|
||||
}
|
||||
function removeHashOnLoad() {
|
||||
if (window.location.hash) {
|
||||
// Store the hash value (without the '#' symbol)
|
||||
var hash = window.location.hash.substring(1);
|
||||
|
||||
// Replace the current URL without the hash
|
||||
var newUrl = window.location.href.replace(window.location.hash, '');
|
||||
|
||||
// Use HTML5 history API to change the URL without reloading the page
|
||||
history.replaceState(null, document.title, newUrl);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Run the function when the window loads
|
||||
window.addEventListener('load', removeHashOnLoad);
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body style="overscroll-behavior: none; overflow: hidden">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root" style="height: 100%;position: absolute;left: px;right: 0px;top:0px;bottom: 0px"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -26,6 +26,7 @@ import ButtonBase, { ButtonBaseProps } from "@mui/material/ButtonBase";
|
||||
|
||||
export interface DraggableButtonBaseProps extends ButtonBaseProps {
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onDoubleClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onLongPressStart?: (currentTarget: HTMLButtonElement, e: React.PointerEvent<HTMLButtonElement> | React.MouseEvent<HTMLButtonElement>) => boolean;
|
||||
onLongPressMove?: (e: React.PointerEvent<HTMLButtonElement>) => void;
|
||||
onLongPressEnd?: (e: React.PointerEvent<HTMLButtonElement>| React.MouseEvent<HTMLButtonElement>) => void;
|
||||
@@ -59,7 +60,7 @@ function screenToClient(e: React.PointerEvent): Point {
|
||||
|
||||
export default function DraggableButtonBase(props: DraggableButtonBaseProps) {
|
||||
// Ensure that the props are spread correctly
|
||||
const { onClick, onLongPressStart, onLongPressMove:
|
||||
const { onClick, onDoubleClick, onLongPressStart, onLongPressMove:
|
||||
doLongPressMove,onLongPressEnd, longPressDelay,instantMouseLongPress, ...rest } = props;
|
||||
|
||||
let [hTimeout, setHTimeout] = React.useState<number | null>(null);
|
||||
@@ -68,6 +69,7 @@ export default function DraggableButtonBase(props: DraggableButtonBaseProps) {
|
||||
let [clickSuppressed, setClickSuppressed] = React.useState<boolean>(false);
|
||||
let [pointerDownPoint, setPointerDownPoint] = React.useState<Point>({ x: 0, y: 0 });
|
||||
let [ longPressedElement, setLongPressedElement ] = React.useState<HTMLButtonElement | null>(null);
|
||||
let [lastClick, setLastClick] = React.useState<number | null>(null);
|
||||
|
||||
|
||||
function handleSuppressClick(e: MouseEvent) {
|
||||
@@ -278,8 +280,26 @@ export default function DraggableButtonBase(props: DraggableButtonBaseProps) {
|
||||
if (onClick) {
|
||||
onClick(e);
|
||||
}
|
||||
// check to see whether this is a double-click
|
||||
let time = Date.now();
|
||||
if (lastClick && time-lastClick < 500) {
|
||||
if (onDoubleClick) {
|
||||
onDoubleClick(e);
|
||||
}
|
||||
setLastClick(null);
|
||||
} else {
|
||||
setLastClick(time);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onDoubleClick={(e) => {
|
||||
// chrome doesnt' do double-click for touch events
|
||||
// so we need to re-implement it on the onclick handler.
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
|
||||
}}
|
||||
onPointerCancelCapture={(e) => {
|
||||
if (pointerId !== null) {
|
||||
e.currentTarget.releasePointerCapture(e.pointerId);
|
||||
|
||||
@@ -31,7 +31,7 @@ import Typography from '@mui/material/Typography';
|
||||
import Input from '@mui/material/Input';
|
||||
import Select from '@mui/material/Select';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import Utility, { nullCast } from './Utility';
|
||||
import Utility from './Utility';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import DialIcon from './svg/fx_dial.svg?react';
|
||||
@@ -231,12 +231,12 @@ const PluginControl =
|
||||
onInputFocus(event: SyntheticEvent): void {
|
||||
//this.displayValueRef.current!.style.display = "none";
|
||||
this.setState({ editFocused: true });
|
||||
if (Utility.hasIMEKeyboard()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.inputRef.current?.blur();
|
||||
this.props.requestIMEEdit(nullCast(this.props.uiControl), this.props.value)
|
||||
}
|
||||
// if (Utility.hasIMEKeyboard()) {
|
||||
// event.preventDefault();
|
||||
// event.stopPropagation();
|
||||
// this.inputRef.current?.blur();
|
||||
// this.props.requestIMEEdit(nullCast(this.props.uiControl), this.props.value)
|
||||
// }
|
||||
}
|
||||
onInputKeyPress(e: any): void {
|
||||
if (e.charCode === 13 && this.inputChanged) {
|
||||
|
||||
@@ -713,24 +713,17 @@ export default function ToobPlayerControl(
|
||||
bottom: 0
|
||||
}}
|
||||
>
|
||||
{/**
|
||||
*
|
||||
sx={{
|
||||
'& .MuiTouchRipple-root': {
|
||||
},
|
||||
'& .MuiTouchRipple-ripple': {
|
||||
transform: 'scale(1.9) !important',
|
||||
}
|
||||
}}
|
||||
*/}
|
||||
<ButtonEx tooltip="Loop Settings" variant="dialogSecondary"
|
||||
style={{
|
||||
flex: "0 1 auto", maxWidth: 200,
|
||||
textTransform: "none", padding: "4px 8px"
|
||||
}}
|
||||
disabled={audioFile === ""}
|
||||
startIcon={(<RepeatIcon />)}
|
||||
onClick={() => {
|
||||
setShowLoopDialog(true);
|
||||
if (audioFile !== "") {
|
||||
setShowLoopDialog(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography noWrap variant="caption">
|
||||
|
||||
Reference in New Issue
Block a user