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()
|
std::vector<AudioFileMetadata> AudioDirectoryInfoImpl::GetFiles()
|
||||||
{
|
{
|
||||||
OpenAudioDb();
|
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;
|
std::vector<AudioFileMetadata> metadataResults;
|
||||||
for (const auto &dbFile : dbFiles)
|
for (const auto &dbFile : dbFiles)
|
||||||
{
|
{
|
||||||
@@ -298,7 +304,16 @@ std::vector<DbFileInfo> AudioDirectoryInfoImpl::UpdateDbFiles()
|
|||||||
|
|
||||||
for (auto dirEntry : fs::directory_iterator(path))
|
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();
|
auto path = dirEntry.path();
|
||||||
std::string name = path.filename();
|
std::string name = path.filename();
|
||||||
|
|||||||
+51
-44
@@ -142,25 +142,27 @@ uint32_t FileMetadataFeature::getFileMetadata(
|
|||||||
json_variant jsonData;
|
json_variant jsonData;
|
||||||
std::string result;
|
std::string result;
|
||||||
|
|
||||||
if (fs::exists(path))
|
try
|
||||||
{
|
{
|
||||||
try
|
if (fs::exists(path))
|
||||||
{
|
{
|
||||||
std::ifstream f(path);
|
std::ifstream f(path);
|
||||||
json_reader reader(f);
|
json_reader reader(f);
|
||||||
reader.read(&jsonData);
|
reader.read(&jsonData);
|
||||||
result = jsonData.as_object()->at(std::string(key)).as_string();
|
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;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t len = result.length() + 1; // +1 for null terminator
|
size_t len = result.length() + 1; // +1 for null terminator
|
||||||
if (len > fileMetadataSize || fileMetadata == nullptr)
|
if (len > fileMetadataSize || fileMetadata == nullptr)
|
||||||
{
|
{
|
||||||
@@ -168,7 +170,7 @@ uint32_t FileMetadataFeature::getFileMetadata(
|
|||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
memcpy(fileMetadata, result.c_str(), 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;
|
return len;
|
||||||
}
|
}
|
||||||
PIPEDAL_FileMetadata_Status FileMetadataFeature::deleteFileMetadata(
|
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));
|
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))
|
if (!fs::exists(path))
|
||||||
{
|
{
|
||||||
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Metadata file does not exist
|
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Metadata file does not exist
|
||||||
}
|
}
|
||||||
|
|
||||||
json_variant jsonData;
|
json_variant jsonData;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::ifstream f(path);
|
std::ifstream f(path);
|
||||||
json_reader reader(f);
|
json_reader reader(f);
|
||||||
reader.read(&jsonData);
|
reader.read(&jsonData);
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Failed to read existing metadata
|
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Failed to read existing metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
auto obj = jsonData.as_object();
|
auto obj = jsonData.as_object();
|
||||||
auto it = obj->find(std::string(key));
|
auto it = obj->find(std::string(key));
|
||||||
if (it == obj->end())
|
if (it == obj->end())
|
||||||
{
|
{
|
||||||
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Key not found
|
return PIPEDAL_FILE_METADATA_NOT_FOUND; // Key not found
|
||||||
}
|
}
|
||||||
|
|
||||||
obj->erase(it);
|
obj->erase(it);
|
||||||
|
|
||||||
if (obj->begin() == obj->end())
|
if (obj->begin() == obj->end())
|
||||||
{
|
{
|
||||||
// If the object is empty, delete the metadata file
|
// If the object is empty, delete the metadata file
|
||||||
fs::remove(path);
|
fs::remove(path);
|
||||||
return PIPEDAL_FILE_METADATA_SUCCESS; // Metadata deleted successfully
|
return PIPEDAL_FILE_METADATA_SUCCESS; // Metadata deleted successfully
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream f(path);
|
std::ofstream f(path);
|
||||||
if (!f.is_open())
|
if (!f.is_open())
|
||||||
{
|
{
|
||||||
Lv2Log::error(SS("Failed to write metadata file " << path));
|
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
|
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(
|
PIPEDAL_FileMetadata_Status FileMetadataFeature::S_setFileMetadata(
|
||||||
|
|||||||
+76
-50
@@ -1690,7 +1690,8 @@ static void AddFilesToResult(
|
|||||||
}
|
}
|
||||||
catch (const std::exception &error)
|
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
|
// sort lexicographically
|
||||||
@@ -1723,7 +1724,7 @@ static void AddTracksToResult(
|
|||||||
std::set<std::string> validExtensions = fileProperty.GetPermittedFileExtensions(
|
std::set<std::string> validExtensions = fileProperty.GetPermittedFileExtensions(
|
||||||
modDirectoryInfo ? modDirectoryInfo->modType : "");
|
modDirectoryInfo ? modDirectoryInfo->modType : "");
|
||||||
|
|
||||||
if (validExtensions.size() == 0)
|
if (validExtensions.size() == 0)
|
||||||
{
|
{
|
||||||
const auto &audioExtensions = MimeTypes::instance().AudioExtensions();
|
const auto &audioExtensions = MimeTypes::instance().AudioExtensions();
|
||||||
validExtensions.insert(audioExtensions.begin(), audioExtensions.end());
|
validExtensions.insert(audioExtensions.begin(), audioExtensions.end());
|
||||||
@@ -1733,54 +1734,76 @@ static void AddTracksToResult(
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Add directories first.
|
// 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());
|
if (!IsValidUtf8(dir_entry.path().string()))
|
||||||
continue; // skip invalid UTF-8 names.
|
{
|
||||||
}
|
Lv2Log::warning("Invalid UTF-8 name in directory: " + dir_entry.path().string());
|
||||||
const auto &path = dir_entry.path();
|
continue; // skip invalid UTF-8 names.
|
||||||
auto name = path.filename().string();
|
}
|
||||||
if (dir_entry.is_directory())
|
const auto &path = dir_entry.path();
|
||||||
{
|
auto name = path.filename().string();
|
||||||
resultFiles.push_back(FileEntry{path, name, true, fs::is_symlink(path)});
|
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();
|
auto collator = Locale::GetInstance()->GetCollator();
|
||||||
std::sort(
|
std::sort(
|
||||||
resultFiles.begin(),
|
resultFiles.begin(),
|
||||||
resultFiles.end(),
|
resultFiles.end(),
|
||||||
[collator](const FileEntry &l, const FileEntry &r)
|
[collator](const FileEntry &l, const FileEntry &r)
|
||||||
{
|
|
||||||
if (l.isDirectory_ != r.isDirectory_)
|
|
||||||
{
|
{
|
||||||
return l.isDirectory_ > r.isDirectory_;
|
if (l.isDirectory_ != r.isDirectory_)
|
||||||
}
|
{
|
||||||
return collator->Compare(l.displayName_ ,r.displayName_) < 0;
|
return l.isDirectory_ > r.isDirectory_;
|
||||||
});
|
}
|
||||||
|
return collator->Compare(l.displayName_, r.displayName_) < 0;
|
||||||
|
});
|
||||||
// Add audio files.
|
// Add audio files.
|
||||||
auto audioFiles = AudioDirectoryInfo::Create(rootPath,
|
auto audioFiles = AudioDirectoryInfo::Create(rootPath,
|
||||||
GetShadowIndexDirectory(audioRootDirectory,rootPath)
|
GetShadowIndexDirectory(audioRootDirectory, rootPath));
|
||||||
);
|
|
||||||
for (const auto &audioFile : audioFiles->GetFiles())
|
try
|
||||||
{
|
{
|
||||||
fs::path audioFilePath = rootPath / audioFile.fileName();
|
for (const auto &audioFile : audioFiles->GetFiles())
|
||||||
std::string extension = UiFileProperty::GetFileExtension(audioFilePath);
|
|
||||||
if (validExtensions.size() == 0 || validExtensions.contains(extension) || validExtensions.contains(".*"))
|
|
||||||
{
|
{
|
||||||
resultFiles.push_back(
|
fs::path audioFilePath = rootPath / audioFile.fileName();
|
||||||
FileEntry(
|
std::string extension = UiFileProperty::GetFileExtension(audioFilePath);
|
||||||
audioFilePath,
|
if (validExtensions.size() == 0 || validExtensions.contains(extension) || validExtensions.contains(".*"))
|
||||||
audioFile.title(),
|
{
|
||||||
false,
|
resultFiles.push_back(
|
||||||
std::make_shared<AudioFileMetadata>(audioFile)));
|
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)
|
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))
|
if (IsInAudioTracksDirectory(relativePath))
|
||||||
{
|
{
|
||||||
AddTracksToResult(this->GetPluginUploadDirectory(),result, rootModDirectory, fileProperty, relativePath);
|
AddTracksToResult(this->GetPluginUploadDirectory(), result, rootModDirectory, fileProperty, relativePath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2046,7 +2069,7 @@ void Storage::DeleteSampleFile(const std::filesystem::path &fileName)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::filesystem::remove(fileName);
|
std::filesystem::remove(fileName);
|
||||||
if (IsInAudioTracksDirectory(fileName))
|
if (IsInAudioTracksDirectory(fileName))
|
||||||
{
|
{
|
||||||
// remove the metadata file as well.
|
// remove the metadata file as well.
|
||||||
std::filesystem::remove(fileName.string() + ".mdata");
|
std::filesystem::remove(fileName.string() + ".mdata");
|
||||||
@@ -2074,13 +2097,12 @@ std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, co
|
|||||||
return result;
|
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()))
|
if (IsInAudioTracksDirectory(fullPath) && isArtworkFileName(fullPath.filename().string()))
|
||||||
{
|
{
|
||||||
// allow artwork files.
|
// allow artwork files.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2208,9 +2230,10 @@ std::string Storage::RenameFilePropertyFile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::rename(oldPath, newPath);
|
std::filesystem::rename(oldPath, newPath);
|
||||||
if (fs::exists(oldPath.string()+".mdata")) {
|
if (fs::exists(oldPath.string() + ".mdata"))
|
||||||
|
{
|
||||||
// rename the metadata file as well.
|
// 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;
|
return newPath;
|
||||||
}
|
}
|
||||||
@@ -2222,12 +2245,13 @@ fs::path MakeVersionedPath(const fs::path &path)
|
|||||||
return path; // no need to version a non-existing file.
|
return path; // no need to version a non-existing file.
|
||||||
}
|
}
|
||||||
fs::path newPath = path;
|
fs::path newPath = path;
|
||||||
auto stem = newPath.stem().string();;
|
auto stem = newPath.stem().string();
|
||||||
|
;
|
||||||
if (stem.ends_with(")"))
|
if (stem.ends_with(")"))
|
||||||
{
|
{
|
||||||
// remove the trailing (n) from the file name.
|
// remove the trailing (n) from the file name.
|
||||||
size_t pos = stem.find_last_of('(');
|
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.
|
// check if it is a number.
|
||||||
if (stemVersion.find_first_not_of("0123456789") == std::string::npos)
|
if (stemVersion.find_first_not_of("0123456789") == std::string::npos)
|
||||||
{
|
{
|
||||||
@@ -2237,7 +2261,7 @@ fs::path MakeVersionedPath(const fs::path &path)
|
|||||||
{
|
{
|
||||||
// remove trailing space.
|
// remove trailing space.
|
||||||
stem = stem.substr(0, stem.length() - 1);
|
stem = stem.substr(0, stem.length() - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2246,12 +2270,15 @@ fs::path MakeVersionedPath(const fs::path &path)
|
|||||||
{
|
{
|
||||||
// append (n) to the file name.
|
// append (n) to the file name.
|
||||||
std::string newFileName;
|
std::string newFileName;
|
||||||
if (version == 0) {
|
if (version == 0)
|
||||||
|
{
|
||||||
newFileName = SS(stem << newPath.extension().string());
|
newFileName = SS(stem << newPath.extension().string());
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// append (n) to the file name.
|
// append (n) to the file name.
|
||||||
newFileName = SS(stem << " (" << std::to_string(version) << ")" << newPath.extension().string());
|
newFileName = SS(stem << " (" << std::to_string(version) << ")" << newPath.extension().string());
|
||||||
}
|
}
|
||||||
newPath = newPath.parent_path() / newFileName;
|
newPath = newPath.parent_path() / newFileName;
|
||||||
++version;
|
++version;
|
||||||
}
|
}
|
||||||
@@ -2280,7 +2307,6 @@ std::string Storage::CopyFilePropertyFile(
|
|||||||
|
|
||||||
std::filesystem::path newPath = this->GetPluginUploadDirectory() / uiFileProperty.directory() / newRelativePath;
|
std::filesystem::path newPath = this->GetPluginUploadDirectory() / uiFileProperty.directory() / newRelativePath;
|
||||||
|
|
||||||
|
|
||||||
if (!this->IsValidSampleFileName(newPath))
|
if (!this->IsValidSampleFileName(newPath))
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Invalid file name.");
|
throw std::runtime_error("Invalid file name.");
|
||||||
@@ -2299,14 +2325,14 @@ std::string Storage::CopyFilePropertyFile(
|
|||||||
|
|
||||||
std::filesystem::create_hard_link(oldPath, newPath);
|
std::filesystem::create_hard_link(oldPath, newPath);
|
||||||
|
|
||||||
if (IsInAudioTracksDirectory(oldPath)
|
if (IsInAudioTracksDirectory(oldPath) && IsInAudioTracksDirectory(newPath))
|
||||||
&& IsInAudioTracksDirectory(newPath))
|
|
||||||
{
|
{
|
||||||
std::filesystem::path metadataPath = SS(oldPath.string() << ".mdata");
|
std::filesystem::path metadataPath = SS(oldPath.string() << ".mdata");
|
||||||
if (fs::exists(metadataPath)) {
|
if (fs::exists(metadataPath))
|
||||||
|
{
|
||||||
// copy the metadata file as well.
|
// copy the metadata file as well.
|
||||||
std::filesystem::copy_file(
|
std::filesystem::copy_file(
|
||||||
metadataPath,
|
metadataPath,
|
||||||
SS(newPath.string() << ".mdata"),
|
SS(newPath.string() << ".mdata"),
|
||||||
fs::copy_options::overwrite_existing);
|
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 PRESET_MIME_TYPE = "application/vnd.pipedal.preset";
|
||||||
static const std::string BANK_MIME_TYPE = "application/vnd.pipedal.bank";
|
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_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_SHORT = "max-age=300,public"; // 5 minutes
|
||||||
|
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
using namespace boost::system;
|
using namespace boost::system;
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
|
||||||
class UserUploadResponse
|
class UserUploadResponse
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -87,7 +85,7 @@ int32_t ConvertThumbnailSize(const std::string ¶m)
|
|||||||
{
|
{
|
||||||
return 0;
|
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)
|
static bool IsZipFile(const std::filesystem::path &path)
|
||||||
@@ -128,7 +126,6 @@ private:
|
|||||||
std::vector<std::string> extensions;
|
std::vector<std::string> extensions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class DownloadIntercept : public RequestHandler
|
class DownloadIntercept : public RequestHandler
|
||||||
{
|
{
|
||||||
PiPedalModel *model;
|
PiPedalModel *model;
|
||||||
@@ -392,16 +389,15 @@ public:
|
|||||||
{
|
{
|
||||||
auto lastModified = std::filesystem::last_write_time(path);
|
auto lastModified = std::filesystem::last_write_time(path);
|
||||||
res.set(HttpField::LastModified, HtmlHelper::timeToHttpDate(lastModified));
|
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,
|
return AudioDirectoryInfo::Create(path,
|
||||||
GetShadowIndexDirectory(
|
GetShadowIndexDirectory(
|
||||||
this->model->GetPluginUploadDirectory(),
|
this->model->GetPluginUploadDirectory(),
|
||||||
path));
|
path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
virtual void get_response(
|
virtual void get_response(
|
||||||
const uri &request_uri,
|
const uri &request_uri,
|
||||||
HttpRequest &req,
|
HttpRequest &req,
|
||||||
@@ -500,89 +496,98 @@ public:
|
|||||||
throw PiPedalException("File not found.");
|
throw PiPedalException("File not found.");
|
||||||
}
|
}
|
||||||
AudioDirectoryInfo::Ptr audioDirectory = CreateDirectoryInfo(path.parent_path());
|
AudioDirectoryInfo::Ptr audioDirectory = CreateDirectoryInfo(path.parent_path());
|
||||||
auto files = audioDirectory->GetFiles();
|
try
|
||||||
for (const auto &file : files)
|
|
||||||
{
|
{
|
||||||
std::string fileNameOnly = path.filename();
|
auto files = audioDirectory->GetFiles();
|
||||||
if (file.fileName() == fileNameOnly)
|
for (const auto &file : files)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::string fileNameOnly = path.filename();
|
||||||
json_writer writer(ss);
|
if (file.fileName() == fileNameOnly)
|
||||||
writer.write(file);
|
{
|
||||||
res.setBody(ss.str());
|
std::stringstream ss;
|
||||||
return;
|
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.
|
// If we get here, the file was not found in the directory.
|
||||||
throw PiPedalException("File not found in directory.");
|
throw PiPedalException("File not found in directory.");
|
||||||
}
|
}
|
||||||
else if (segment == "Thumbnail")
|
else if (segment == "Thumbnail")
|
||||||
{
|
{
|
||||||
|
ThumbnailTemporaryFile thumbnailTemporaryFile;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
fs::path path = request_uri.query("path");
|
std::shared_ptr<TemporaryFile> thumbnail;
|
||||||
if (path.empty() ||
|
try
|
||||||
!fs::exists(path) ||
|
|
||||||
!this->model->IsInUploadsDirectory(path) ||
|
|
||||||
HasDotDot(path))
|
|
||||||
{
|
{
|
||||||
// path for folder thumbnails.
|
fs::path path = request_uri.query("path");
|
||||||
path = request_uri.query("ffile");
|
if (path.empty() ||
|
||||||
|
!fs::exists(path) ||
|
||||||
std::shared_ptr<TemporaryFile> thumbnail;
|
!this->model->IsInUploadsDirectory(path) ||
|
||||||
|
HasDotDot(path))
|
||||||
if (!path.empty() && fs::exists(path) &&
|
|
||||||
this->model->IsInUploadsDirectory(path) &&
|
|
||||||
!HasDotDot(path))
|
|
||||||
{
|
{
|
||||||
// path is a folder.
|
std::shared_ptr<TemporaryFile> thumbnail;
|
||||||
thumbnail = std::make_shared<TemporaryFile>();
|
// path for folder thumbnails.
|
||||||
thumbnail->SetNonDeletedPath(path);
|
path = request_uri.query("ffile");
|
||||||
}
|
|
||||||
else
|
if (!path.empty() && fs::exists(path) &&
|
||||||
{
|
this->model->IsInUploadsDirectory(path) &&
|
||||||
auto t = AudioDirectoryInfo::DefaultThumbnailTemporaryFile();
|
!HasDotDot(path))
|
||||||
thumbnail = std::make_shared<TemporaryFile>();
|
{
|
||||||
thumbnail->SetNonDeletedPath(t.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()));
|
int32_t width = ConvertThumbnailSize(request_uri.query("w"));
|
||||||
res.set(HttpField::cache_control, CACHE_CONTROL_INDEFINITELY); // URL is cache-busted, and will change if the file ismodified.
|
int32_t height = ConvertThumbnailSize(request_uri.query("h"));
|
||||||
setLastModifiedFromFile(res,thumbnail->Path());
|
|
||||||
res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnail->Path())));
|
|
||||||
res.setBodyFile(thumbnail);
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
int32_t width = ConvertThumbnailSize(request_uri.query("w"));
|
fs::path defaultThumbnail = model->GetWebRoot() / "img/missing_thumbnail.jpg";
|
||||||
int32_t height = ConvertThumbnailSize(request_uri.query("h"));
|
thumbnailTemporaryFile.SetNonDeletedPath(defaultThumbnail, "image/jpeg");
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
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.
|
res.set(HttpField::cache_control, CACHE_CONTROL_INDEFINITELY); // URL is cache-busted with time-stamp.
|
||||||
setLastModifiedFromFile(res, path);
|
setLastModifiedFromFile(res, thumbnailTemporaryFile.Path());
|
||||||
res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnail.Path())));
|
res.set(HttpField::content_length, std::to_string(fs::file_size(thumbnailTemporaryFile.Path())));
|
||||||
|
|
||||||
std::filesystem::path t = thumbnail.Path();
|
std::filesystem::path t = thumbnailTemporaryFile.Path();
|
||||||
res.setBodyFile(t,thumbnail.DeleteFile());
|
res.setBodyFile(t, thumbnailTemporaryFile.DeleteFile());
|
||||||
thumbnail.Detach();
|
thumbnailTemporaryFile.Detach();
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
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;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -605,11 +610,11 @@ public:
|
|||||||
{
|
{
|
||||||
result = (directoryPath / result).string();
|
result = (directoryPath / result).string();
|
||||||
}
|
}
|
||||||
|
|
||||||
// json-encode the result.
|
// json-encode the result.
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
json_writer writer(ss);
|
json_writer writer(ss);
|
||||||
writer.write(result);
|
writer.write(result);
|
||||||
|
|
||||||
res.setBody(ss.str());
|
res.setBody(ss.str());
|
||||||
}
|
}
|
||||||
@@ -637,11 +642,11 @@ public:
|
|||||||
{
|
{
|
||||||
result = (directoryPath / result).string();
|
result = (directoryPath / result).string();
|
||||||
}
|
}
|
||||||
|
|
||||||
// json-encode the result.
|
// json-encode the result.
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
json_writer writer(ss);
|
json_writer writer(ss);
|
||||||
writer.write(result);
|
writer.write(result);
|
||||||
|
|
||||||
res.setBody(ss.str());
|
res.setBody(ss.str());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ add_custom_command(
|
|||||||
public/var/current_pedalboard.json
|
public/var/current_pedalboard.json
|
||||||
public/var/config.json
|
public/var/config.json
|
||||||
public/favicon.ico
|
public/favicon.ico
|
||||||
public/index.html
|
|
||||||
public/iso_codes.json
|
public/iso_codes.json
|
||||||
public/logo512.png
|
public/logo512.png
|
||||||
public/sample_lv2_plugins.json
|
public/sample_lv2_plugins.json
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<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="theme-color" content="#000000" />
|
||||||
<meta name="color-scheme" content="dark light"/> <!-- uses media queries in web views. -->
|
<meta name="color-scheme" content="dark light"/> <!-- uses media queries in web views. -->
|
||||||
<meta name="description" content="PiPedal Guitar Stomp Box for Raspberry Pi" />
|
<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 {
|
export interface DraggableButtonBaseProps extends ButtonBaseProps {
|
||||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
onDoubleClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
onLongPressStart?: (currentTarget: HTMLButtonElement, e: React.PointerEvent<HTMLButtonElement> | React.MouseEvent<HTMLButtonElement>) => boolean;
|
onLongPressStart?: (currentTarget: HTMLButtonElement, e: React.PointerEvent<HTMLButtonElement> | React.MouseEvent<HTMLButtonElement>) => boolean;
|
||||||
onLongPressMove?: (e: React.PointerEvent<HTMLButtonElement>) => void;
|
onLongPressMove?: (e: React.PointerEvent<HTMLButtonElement>) => void;
|
||||||
onLongPressEnd?: (e: React.PointerEvent<HTMLButtonElement>| React.MouseEvent<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) {
|
export default function DraggableButtonBase(props: DraggableButtonBaseProps) {
|
||||||
// Ensure that the props are spread correctly
|
// Ensure that the props are spread correctly
|
||||||
const { onClick, onLongPressStart, onLongPressMove:
|
const { onClick, onDoubleClick, onLongPressStart, onLongPressMove:
|
||||||
doLongPressMove,onLongPressEnd, longPressDelay,instantMouseLongPress, ...rest } = props;
|
doLongPressMove,onLongPressEnd, longPressDelay,instantMouseLongPress, ...rest } = props;
|
||||||
|
|
||||||
let [hTimeout, setHTimeout] = React.useState<number | null>(null);
|
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 [clickSuppressed, setClickSuppressed] = React.useState<boolean>(false);
|
||||||
let [pointerDownPoint, setPointerDownPoint] = React.useState<Point>({ x: 0, y: 0 });
|
let [pointerDownPoint, setPointerDownPoint] = React.useState<Point>({ x: 0, y: 0 });
|
||||||
let [ longPressedElement, setLongPressedElement ] = React.useState<HTMLButtonElement | null>(null);
|
let [ longPressedElement, setLongPressedElement ] = React.useState<HTMLButtonElement | null>(null);
|
||||||
|
let [lastClick, setLastClick] = React.useState<number | null>(null);
|
||||||
|
|
||||||
|
|
||||||
function handleSuppressClick(e: MouseEvent) {
|
function handleSuppressClick(e: MouseEvent) {
|
||||||
@@ -278,8 +280,26 @@ export default function DraggableButtonBase(props: DraggableButtonBaseProps) {
|
|||||||
if (onClick) {
|
if (onClick) {
|
||||||
onClick(e);
|
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) => {
|
onPointerCancelCapture={(e) => {
|
||||||
if (pointerId !== null) {
|
if (pointerId !== null) {
|
||||||
e.currentTarget.releasePointerCapture(e.pointerId);
|
e.currentTarget.releasePointerCapture(e.pointerId);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import Typography from '@mui/material/Typography';
|
|||||||
import Input from '@mui/material/Input';
|
import Input from '@mui/material/Input';
|
||||||
import Select from '@mui/material/Select';
|
import Select from '@mui/material/Select';
|
||||||
import Switch from '@mui/material/Switch';
|
import Switch from '@mui/material/Switch';
|
||||||
import Utility, { nullCast } from './Utility';
|
import Utility from './Utility';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||||
import DialIcon from './svg/fx_dial.svg?react';
|
import DialIcon from './svg/fx_dial.svg?react';
|
||||||
@@ -231,12 +231,12 @@ const PluginControl =
|
|||||||
onInputFocus(event: SyntheticEvent): void {
|
onInputFocus(event: SyntheticEvent): void {
|
||||||
//this.displayValueRef.current!.style.display = "none";
|
//this.displayValueRef.current!.style.display = "none";
|
||||||
this.setState({ editFocused: true });
|
this.setState({ editFocused: true });
|
||||||
if (Utility.hasIMEKeyboard()) {
|
// if (Utility.hasIMEKeyboard()) {
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
event.stopPropagation();
|
// event.stopPropagation();
|
||||||
this.inputRef.current?.blur();
|
// this.inputRef.current?.blur();
|
||||||
this.props.requestIMEEdit(nullCast(this.props.uiControl), this.props.value)
|
// this.props.requestIMEEdit(nullCast(this.props.uiControl), this.props.value)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
onInputKeyPress(e: any): void {
|
onInputKeyPress(e: any): void {
|
||||||
if (e.charCode === 13 && this.inputChanged) {
|
if (e.charCode === 13 && this.inputChanged) {
|
||||||
|
|||||||
@@ -713,24 +713,17 @@ export default function ToobPlayerControl(
|
|||||||
bottom: 0
|
bottom: 0
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/**
|
|
||||||
*
|
|
||||||
sx={{
|
|
||||||
'& .MuiTouchRipple-root': {
|
|
||||||
},
|
|
||||||
'& .MuiTouchRipple-ripple': {
|
|
||||||
transform: 'scale(1.9) !important',
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
*/}
|
|
||||||
<ButtonEx tooltip="Loop Settings" variant="dialogSecondary"
|
<ButtonEx tooltip="Loop Settings" variant="dialogSecondary"
|
||||||
style={{
|
style={{
|
||||||
flex: "0 1 auto", maxWidth: 200,
|
flex: "0 1 auto", maxWidth: 200,
|
||||||
textTransform: "none", padding: "4px 8px"
|
textTransform: "none", padding: "4px 8px"
|
||||||
}}
|
}}
|
||||||
|
disabled={audioFile === ""}
|
||||||
startIcon={(<RepeatIcon />)}
|
startIcon={(<RepeatIcon />)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowLoopDialog(true);
|
if (audioFile !== "") {
|
||||||
|
setShowLoopDialog(true);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography noWrap variant="caption">
|
<Typography noWrap variant="caption">
|
||||||
|
|||||||
Reference in New Issue
Block a user