Toob Player UI

This commit is contained in:
Robin E. R. Davies
2025-06-07 21:52:23 -04:00
parent 82f94cb152
commit 6ea45f46df
29 changed files with 1860 additions and 319 deletions
+149 -30
View File
@@ -30,6 +30,9 @@
#include "MimeTypes.hpp"
#include <stdexcept>
#include "AudioFilesDb.hpp"
#include "Lv2Log.hpp"
#include "ss.hpp"
#include "util.hpp"
#undef _GLIBCXX_DEBUG // Ensure we are not in debug mode, as this file is not compatible with it.
#include "SQLiteCpp/SQLiteCpp.h"
@@ -48,7 +51,7 @@ void AudioDirectoryInfo::SetResourceDirectory(const std::filesystem::path &path)
{
resourceDirectory = path;
}
std::filesystem::path AudioDirectoryInfo::GetTemporaryDirectory() const
std::filesystem::path AudioDirectoryInfo::GetTemporaryDirectory()
{
if (temporaryDirectory.empty())
{
@@ -56,7 +59,7 @@ std::filesystem::path AudioDirectoryInfo::GetTemporaryDirectory() const
}
return temporaryDirectory;
}
std::filesystem::path AudioDirectoryInfo::GetResourceDirectory() const
std::filesystem::path AudioDirectoryInfo::GetResourceDirectory()
{
if (resourceDirectory.empty())
{
@@ -74,7 +77,9 @@ namespace
static int64_t fileTimeToInt64(const fs::file_time_type &fileTime)
{
return fileTime.time_since_epoch().count();
std::chrono::system_clock::time_point system_time = std::chrono::clock_cast<std::chrono::system_clock>(fileTime);
auto result = std::chrono::duration_cast<std::chrono::milliseconds>(system_time.time_since_epoch()).count();
return result;
}
static int64_t GetLastWriteTime(const fs::path &file)
{
@@ -91,9 +96,15 @@ namespace
class AudioDirectoryInfoImpl : public AudioDirectoryInfo
{
public:
AudioDirectoryInfoImpl(const std::string &path)
: path(path)
AudioDirectoryInfoImpl(const std::filesystem::path &path, const std::filesystem::path&indexPath)
: path(path), indexPath(
(indexPath.empty() ? path: indexPath) / ".index.pipedal"
)
{
if (this->indexPath.parent_path() != path)
{
fs::create_directories(this->indexPath.parent_path());
}
}
virtual ~AudioDirectoryInfoImpl() {}
@@ -110,7 +121,16 @@ namespace
virtual std::string GetNextAudioFile(const std::string &fileNameOnly) override;
virtual std::string GetPreviousAudioFile(const std::string &fileNameOnly) override;
virtual void MoveAudioFile(
const std::string &directory,
int32_t fromPosition,
int32_t toPosition) override;
private:
std::vector<DbFileInfo> QueryTracks();
bool opened = false;
std::filesystem::path indexPath;
void OpenAudioDb();
ThumbnailTemporaryFile GetUnindexedThunbnail(const std::string &fileNameOnly, int32_t width, int32_t height);
@@ -119,7 +139,6 @@ namespace
fs::path path;
using id_t = int64_t;
std::vector<DbFileInfo> QueryTracks();
std::vector<DbFileInfo> UpdateDbFiles();
void DbSetThumbnailType(
@@ -137,14 +156,23 @@ namespace
void UpdateMetadata(DbFileInfo *dbFile);
static constexpr int DB_VERSION = 1;
std::filesystem::path GetFolderFile() const;
};
}
AudioDirectoryInfo::Ptr AudioDirectoryInfo::Create(const std::string &path)
AudioDirectoryInfo::Ptr AudioDirectoryInfo::Create(const std::filesystem::path &path, const std::filesystem::path&indexPath)
{
return std::shared_ptr<AudioDirectoryInfo>(
new AudioDirectoryInfoImpl(path));
return std::make_shared<AudioDirectoryInfoImpl>(path,indexPath);
}
std::vector<DbFileInfo> AudioDirectoryInfoImpl::QueryTracks()
{
if (audioFilesDb)
{
return audioFilesDb->QueryTracks();
}
return std::vector<DbFileInfo>();
}
std::vector<AudioFileMetadata> AudioDirectoryInfoImpl::GetFiles()
@@ -160,14 +188,6 @@ std::vector<AudioFileMetadata> AudioDirectoryInfoImpl::GetFiles()
return metadataResults;
}
std::vector<DbFileInfo> AudioDirectoryInfoImpl::QueryTracks()
{
if (audioFilesDb)
{
return audioFilesDb->QueryTracks();
}
return std::vector<DbFileInfo>();
}
static bool isAudioExtension(const std::string &extension)
{
return MimeTypes::instance().AudioExtensions().contains(extension);
@@ -340,12 +360,6 @@ std::vector<DbFileInfo> AudioDirectoryInfoImpl::UpdateDbFiles()
}
}
if (!updateRequired && newFiles.empty())
{
// Nothing to do.
return dbFiles;
}
Locale::ptr locale = Locale::GetInstance();
Collator::ptr collator = locale->GetCollator();
if (!HasPositionInfo(dbFiles))
@@ -415,7 +429,8 @@ static std::vector<std::filesystem::path> COVER_ART_FILES = {
"AlbumArt.jpg",
"albumArt.jpg",
"Frontcover.jpg",
"AlbumArtSmall.jpg"};
"AlbumArtSmall.jpg" // windows media player.
};
fs::path AudioDirectoryInfoImpl::GetFolderFile() const
{
@@ -446,7 +461,7 @@ void AudioDirectoryInfoImpl::DbSetThumbnailType(
}
}
void AudioDirectoryInfoImpl::DbSetThumbnailType(
const std::string&fileNameOnly,
const std::string &fileNameOnly,
ThumbnailType thumbnailType,
const std::string &thumbnailFile,
int64_t thumbnailLastModified)
@@ -614,6 +629,8 @@ void AudioDirectoryInfoImpl::UpdateMetadata(DbFileInfo *dbFile)
dbFile->track(metadata.track());
dbFile->duration(metadata.duration());
dbFile->album(metadata.album());
dbFile->artist(metadata.artist());
dbFile->albumArtist(metadata.albumArtist());
dbFile->lastModified(GetLastWriteTime(file));
dbFile->duration(metadata.duration());
}
@@ -656,8 +673,17 @@ size_t AudioDirectoryInfoImpl::TestGetNumberOfThumbnails()
void AudioDirectoryInfoImpl::OpenAudioDb()
{
if (!this->audioFilesDb) {
this->audioFilesDb = std::make_shared<AudioFilesDb>(this->path, indexPath);
if (!this->audioFilesDb)
{
try
{
this->audioFilesDb = std::make_shared<AudioFilesDb>(this->path, indexPath);
}
catch (const SQLite::Exception &e)
{
Lv2Log::debug("Can't create .index.pipedal: %s - %s", e.what(), this->path.c_str());
}
}
}
@@ -669,7 +695,7 @@ ThumbnailTemporaryFile AudioDirectoryInfoImpl::DefaultThumbnailTemporaryFile()
return tempFile;
}
std::string AudioDirectoryInfoImpl::GetNextAudioFile(const std::string &fileNameOnly)
std::string AudioDirectoryInfoImpl::GetNextAudioFile(const std::string &fileNameOnly)
{
auto files = this->GetFiles();
if (files.empty())
@@ -692,7 +718,7 @@ std::string AudioDirectoryInfoImpl::GetNextAudioFile(const std::string &fileName
}
return "";
}
std::string AudioDirectoryInfoImpl::GetPreviousAudioFile(const std::string &fileNameOnly)
std::string AudioDirectoryInfoImpl::GetPreviousAudioFile(const std::string &fileNameOnly)
{
auto files = this->GetFiles();
if (files.empty())
@@ -715,3 +741,96 @@ std::string AudioDirectoryInfoImpl::GetPreviousAudioFile(const std::string &file
}
return "";
}
void AudioDirectoryInfoImpl::MoveAudioFile(
const std::string &directory,
int32_t fromPosition,
int32_t toPosition)
{
OpenAudioDb();
if (!audioFilesDb)
{
throw std::runtime_error("Directory is not writable.");
}
auto files = this->UpdateDbFiles();
if (directory.empty() || fromPosition < 0 || toPosition < 0 ||
fromPosition >= static_cast<int32_t>(files.size()) ||
toPosition > static_cast<int32_t>(files.size()))
{
throw std::invalid_argument("Invalid arguments for MoveAudioFile");
}
std::shared_ptr<SQLite::Transaction> transaction;
if (audioFilesDb)
{
transaction = audioFilesDb->transaction();
}
if (fromPosition == toPosition)
{
return; // No change needed.
}
if (fromPosition > toPosition)
{
auto t = files[fromPosition];
files.erase(files.begin() + fromPosition);
files.insert(files.begin() + toPosition, t);
}
else
{
// fromPosition < toPosition
auto t = files[fromPosition];
files.erase(files.begin() + fromPosition);
files.insert(files.begin() + toPosition, t); // insert before the toPosition.
}
for (size_t i = 0; i < files.size(); ++i)
{
auto &file = files[i];
if (file.position() != static_cast<int32_t>(i))
{
file.position(static_cast<int32_t>(i));
if (this->audioFilesDb)
{
audioFilesDb->UpdateFilePosition(file.idFile(), file.position());
}
else
{
throw std::runtime_error("Directory is not writable.");
}
}
}
transaction->commit();
}
namespace pipedal {
std::filesystem::path IndexPathToShadowIndexPath(const std::filesystem::path &audioRootDirectory, const std::filesystem::path &path)
{
std::filesystem::path relativePath = MakeRelativePath(path, audioRootDirectory);
if (relativePath.is_absolute())
{
throw std::runtime_error(SS("Can't write to path " << path << "."));
};
return audioRootDirectory / "shadow_indexes" / relativePath;
}
// recover the original path fromthe shadow index path.
std::filesystem::path ShadowIndexPathToIndexPath(const std::filesystem::path &audioRootDirectory, const std::filesystem::path &path)
{
auto shadowIndexesPath = audioRootDirectory / "shadow_indexes";
std::filesystem::path relativePath = MakeRelativePath(path, shadowIndexesPath);
if (relativePath.is_absolute())
{
throw std::runtime_error(SS("Path must start with " << shadowIndexesPath));
}
return audioRootDirectory / relativePath; // recover the original path.
}
std::filesystem::path GetShadowIndexDirectory(const std::filesystem::path &audioRootDirectory, const std::filesystem::path &path)
{
if (HasWritePermissions(path))
{
return ""; // use default index path.
}
return IndexPathToShadowIndexPath(audioRootDirectory, path);
}
}