Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
19 kB · 446 lines
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447#include "pch.h"#include "PhotoDiscService.hpp"
#include <cctype>
using namespace Platform;using namespace Windows::Foundation;using namespace Windows::Foundation::Collections;using namespace Windows::Graphics::Imaging;using namespace Windows::Storage;using namespace Windows::Storage::Streams;using namespace concurrency;
namespace NativeBios {namespace {
constexpr std::size_t kMaxPhotoFiles = 4096;constexpr unsigned kMaxFolderDepth = 32;constexpr std::uint64_t kMaxEncodedBytes = 128ull * 1024 * 1024;constexpr unsigned kMaxDecodedSide = 2048;
std::string utf8(String^ value) { if (!value || value->IsEmpty()) return {}; const int size = WideCharToMultiByte(CP_UTF8, 0, value->Data(), value->Length(), nullptr, 0, nullptr, nullptr); std::string result(static_cast<std::size_t>(size), '\0'); WideCharToMultiByte(CP_UTF8, 0, value->Data(), value->Length(), result.data(), size, nullptr, nullptr); return result;}
bool is_photo_file(StorageFile^ file) { if (!file) return false; auto extension = utf8(file->FileType); std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char value) { return static_cast<char>(std::tolower(value)); }); return extension == ".jpg" || extension == ".jpeg" || extension == ".jpe" || extension == ".png" || extension == ".tif" || extension == ".tiff" || extension == ".pcd";}
bool is_photo_path(const std::wstring& path) { const auto dot = path.find_last_of(L'.'); if (dot == std::wstring::npos) return false; auto extension = path.substr(dot); std::transform(extension.begin(), extension.end(), extension.begin(), [](wchar_t value) { return static_cast<wchar_t>(std::towlower(value)); }); return extension == L".jpg" || extension == L".jpeg" || extension == L".jpe" || extension == L".png" || extension == L".tif" || extension == L".tiff" || extension == L".pcd";}
std::string clean_error(String^ value) { auto result = utf8(value); for (auto& character : result) if (character == '\r' || character == '\n') character = ' '; if (result.size() > 512) result.resize(512); return result;}
} // namespace
PhotoDiscService::PhotoDiscService(ac::xbox::Api& api, ImageReady image_ready, Logger logger) : m_api(api), m_imageReady(std::move(image_ready)), m_log(std::move(logger)) { m_api.disc.scan = [this]() { scan(); }; m_api.disc.show = [this](std::int64_t index) { show(index); }; m_api.disc.copy = [this]() { copy_all(); };}
void PhotoDiscService::update( const std::function<void(ac::xbox::PhotoDiscSnapshot&)>& edit) { const auto current = std::atomic_load(&m_api.disc.snapshot); auto next = std::make_shared<ac::xbox::PhotoDiscSnapshot>(); if (current) *next = *current; edit(*next); std::atomic_store(&m_api.disc.snapshot, std::static_pointer_cast<const ac::xbox::PhotoDiscSnapshot>(next));}
task<void> PhotoDiscService::collect(StorageFolder^ folder, const std::shared_ptr<std::vector<StorageFile^>>& output, unsigned depth) { if (!folder || depth > kMaxFolderDepth || output->size() >= kMaxPhotoFiles) return task_from_result(); return create_task(folder->GetFilesAsync()).then( [folder, output](IVectorView<StorageFile^>^ files) { for (auto file : files) { if (output->size() >= kMaxPhotoFiles) break; if (is_photo_file(file)) output->push_back(file); } return create_task(folder->GetFoldersAsync()); }).then([this, output, depth](IVectorView<StorageFolder^>^ folders) { task<void> chain = task_from_result(); for (auto child : folders) { chain = chain.then([this, child, output, depth]() { return collect(child, output, depth + 1); }); } return chain; });}
task<void> PhotoDiscService::collect_optical_drive_letters( const std::shared_ptr<std::vector<StorageFile^>>& output, const std::shared_ptr<std::vector<std::string>>& volume_names) { task<void> chain = task_from_result(); unsigned candidates = 0; for (wchar_t letter = L'D'; letter <= L'Z'; ++letter) { wchar_t root[] = {letter, L':', L'\\', L'\0'}; const auto type = GetDriveTypeW(root); if (type != DRIVE_CDROM) continue; ++candidates; const std::wstring path(root); if (m_log) m_log("AC_NATIVE_DISC_OPTICAL_ROOT path=" + std::string(1, static_cast<char>(letter)) + ":\\"); chain = chain.then([this, output, volume_names, path]() { return create_task(StorageFolder::GetFolderFromPathAsync( ref new String(path.c_str()))).then( [this, output, volume_names, path](task<StorageFolder^> completed) { try { auto folder = completed.get(); volume_names->push_back(utf8(folder->Name)); return collect(folder, output, 0); } catch (Exception^ error) { if (m_log) m_log("AC_NATIVE_DISC_OPTICAL_OPEN_ERROR path=" + std::string(1, static_cast<char>(path[0])) + ":\\ error=" + clean_error(error->Message)); return collect_win32_optical_root(path, output, volume_names); } return task_from_result(); }); }); } if (candidates == 0 && m_log) m_log("AC_NATIVE_DISC_OPTICAL_ROOT count=0"); return chain;}
bool PhotoDiscService::collect_win32_paths(const std::wstring& folder, const std::shared_ptr<std::vector<std::wstring>>& output, unsigned depth) { if (depth > kMaxFolderDepth || output->size() >= kMaxPhotoFiles) return true; std::wstring pattern = folder; if (!pattern.empty() && pattern.back() != L'\\') pattern += L'\\'; pattern += L'*'; WIN32_FIND_DATAW found{}; const auto handle = FindFirstFileExFromAppW(pattern.c_str(), FindExInfoBasic, &found, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH); if (handle == INVALID_HANDLE_VALUE) { if (depth == 0 && m_log) m_log("AC_NATIVE_DISC_WIN32_ENUM_ERROR path=" + std::string(1, static_cast<char>(folder[0])) + ":\\ win32=" + std::to_string(GetLastError())); return false; } do { const std::wstring name(found.cFileName); if (name == L"." || name == L"..") continue; std::wstring path = folder; if (!path.empty() && path.back() != L'\\') path += L'\\'; path += name; if ((found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { if ((found.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) collect_win32_paths(path, output, depth + 1); } else if (is_photo_path(path)) { output->push_back(std::move(path)); } } while (output->size() < kMaxPhotoFiles && FindNextFileW(handle, &found)); FindClose(handle); return true;}
task<void> PhotoDiscService::collect_win32_optical_root(const std::wstring& root, const std::shared_ptr<std::vector<StorageFile^>>& output, const std::shared_ptr<std::vector<std::string>>& volume_names) { auto paths = std::make_shared<std::vector<std::wstring>>(); if (!collect_win32_paths(root, paths, 0)) return task_from_result(); volume_names->push_back(std::string(1, static_cast<char>(root[0])) + ":\\"); if (m_log) m_log("AC_NATIVE_DISC_WIN32_ENUM_READY path=" + std::string(1, static_cast<char>(root[0])) + ":\\ photos=" + std::to_string(paths->size())); task<void> chain = task_from_result(); for (const auto& path : *paths) { chain = chain.then([this, output, path]() { return create_task(StorageFile::GetFileFromPathAsync( ref new String(path.c_str()))).then([this, output, path](task<StorageFile^> completed) { try { output->push_back(completed.get()); } catch (Exception^ error) { if (m_log) m_log("AC_NATIVE_DISC_WIN32_FILE_ERROR name=" + utf8(ref new String(path.substr(path.find_last_of(L'\\') + 1).c_str())) + " error=" + clean_error(error->Message)); } }); }); } return chain;}
void PhotoDiscService::fail_scan(const std::string& message) { update([&message](ac::xbox::PhotoDiscSnapshot& snapshot) { snapshot.status = "error: " + message; snapshot.count = 0; snapshot.current_ready = false; }); if (m_log) m_log("AC_NATIVE_DISC_ERROR " + message);}
void PhotoDiscService::scan() { bool expected = false; if (!m_scanInFlight.compare_exchange_strong(expected, true)) return; ++m_loadGeneration; { std::lock_guard<std::mutex> lock(m_filesMutex); m_files.clear(); m_volume.clear(); } update([](ac::xbox::PhotoDiscSnapshot& snapshot) { snapshot = {}; snapshot.status = "scanning"; }); if (m_log) m_log("AC_NATIVE_DISC_SCAN begin=1");
auto photos = std::make_shared<std::vector<StorageFile^>>(); auto volumeNames = std::make_shared<std::vector<std::string>>(); create_task(KnownFolders::RemovableDevices->GetFoldersAsync()).then( [this, photos, volumeNames](IVectorView<StorageFolder^>^ volumes) { if (!volumes || volumes->Size == 0) { if (m_log) m_log("AC_NATIVE_DISC_REMOVABLE_ROOT count=0 fallback=drive-letter"); return collect_optical_drive_letters(photos, volumeNames); } task<void> chain = task_from_result(); for (auto volume : volumes) { volumeNames->push_back(utf8(volume->Name)); chain = chain.then([this, volume, photos]() { return collect(volume, photos, 0); }); } return chain; }).then([this, photos, volumeNames](task<void> completed) { try { completed.get(); if (volumeNames->empty()) throw std::runtime_error("no mounted optical or removable volume"); std::sort(photos->begin(), photos->end(), [](StorageFile^ left, StorageFile^ right) { return utf8(left ? left->Path : nullptr) < utf8(right ? right->Path : nullptr); }); std::string volume; for (const auto& name : *volumeNames) { if (!volume.empty()) volume += ", "; volume += name; } { std::lock_guard<std::mutex> lock(m_filesMutex); m_files = *photos; m_volume = volume; } update([photos, &volume](ac::xbox::PhotoDiscSnapshot& snapshot) { snapshot.status = photos->empty() ? "empty" : "ready"; snapshot.volume = volume; snapshot.count = photos->size(); snapshot.index = 0; snapshot.current_ready = false; }); if (m_log) m_log("AC_NATIVE_DISC_READY volumes=" + std::to_string(volumeNames->size()) + " photos=" + std::to_string(photos->size()) + " formats=jpg,jpeg,jpe,png,tif,tiff,pcd"); m_scanInFlight = false; if (!photos->empty()) show(0); return; } catch (Exception^ error) { fail_scan(clean_error(error->Message)); } catch (const std::exception& error) { fail_scan(error.what()); } m_scanInFlight = false; });}
void PhotoDiscService::show(std::int64_t requested_index) { StorageFile^ file = nullptr; std::size_t index = 0; std::size_t count = 0; { std::lock_guard<std::mutex> lock(m_filesMutex); count = m_files.size(); if (count == 0) return; const auto modulus = static_cast<std::int64_t>(count); index = static_cast<std::size_t>((requested_index % modulus + modulus) % modulus); file = m_files[index]; } if (!file) return; const auto generation = ++m_loadGeneration; const auto name = utf8(file->Name); update([index, count, &name](ac::xbox::PhotoDiscSnapshot& snapshot) { snapshot.status = "loading"; snapshot.index = index; snapshot.count = count; snapshot.name = name; snapshot.width = 0; snapshot.height = 0; snapshot.current_ready = false; }); if (m_log) m_log("AC_NATIVE_DISC_LOAD index=" + std::to_string(index) + " name=" + name);
create_task(file->OpenAsync(FileAccessMode::Read)).then( [](IRandomAccessStream^ stream) { if (!stream || stream->Size == 0 || stream->Size > kMaxEncodedBytes) throw std::runtime_error("photo payload is empty or exceeds 128 MiB"); return create_task(BitmapDecoder::CreateAsync(stream)).then( [stream](BitmapDecoder^ decoder) { if (!decoder || decoder->PixelWidth == 0 || decoder->PixelHeight == 0) throw std::runtime_error("image decoder returned an empty frame"); const double scale = (std::min)(1.0, kMaxDecodedSide / static_cast<double>((std::max)(decoder->PixelWidth, decoder->PixelHeight))); const unsigned width = (std::max)(1u, static_cast<unsigned>(decoder->PixelWidth * scale)); const unsigned height = (std::max)(1u, static_cast<unsigned>(decoder->PixelHeight * scale)); auto transform = ref new BitmapTransform(); transform->ScaledWidth = width; transform->ScaledHeight = height; return create_task(decoder->GetPixelDataAsync(BitmapPixelFormat::Bgra8, BitmapAlphaMode::Straight, transform, ExifOrientationMode::RespectExifOrientation, ColorManagementMode::ColorManageToSRgb)).then( [stream, width, height](PixelDataProvider^ provider) { const auto bytes = provider->DetachPixelData(); const auto pixelCount = static_cast<std::size_t>(width) * height; if (!bytes || bytes->Length < pixelCount * 4) throw std::runtime_error("image decoder returned a short pixel buffer"); auto image = std::make_shared<PhotoDiscImage>(); image->width = width; image->height = height; image->pixels.resize(pixelCount); for (std::size_t i = 0; i < pixelCount; ++i) { const auto offset = i * 4; image->pixels[i] = (static_cast<std::uint32_t>(bytes[offset + 3]) << 24) | (static_cast<std::uint32_t>(bytes[offset + 2]) << 16) | (static_cast<std::uint32_t>(bytes[offset + 1]) << 8) | static_cast<std::uint32_t>(bytes[offset]); } return image; }); }); }).then([this, generation, index, count, name]( task<std::shared_ptr<PhotoDiscImage>> completed) { if (generation != m_loadGeneration.load()) return; try { auto image = completed.get(); if (m_imageReady) m_imageReady(std::static_pointer_cast<const PhotoDiscImage>(image)); update([index, count, &name, &image](ac::xbox::PhotoDiscSnapshot& snapshot) { snapshot.status = "ready"; snapshot.index = index; snapshot.count = count; snapshot.name = name; snapshot.width = image->width; snapshot.height = image->height; snapshot.current_ready = true; }); if (m_log) m_log("AC_NATIVE_DISC_IMAGE_READY index=" + std::to_string(index) + " size=" + std::to_string(image->width) + "x" + std::to_string(image->height) + " name=" + name); } catch (Exception^ error) { const auto message = clean_error(error->Message); update([&message](ac::xbox::PhotoDiscSnapshot& snapshot) { snapshot.status = "decode-error: " + message; snapshot.current_ready = false; }); if (m_log) m_log("AC_NATIVE_DISC_DECODE_ERROR " + message); } catch (const std::exception& error) { const std::string message(error.what()); update([&message](ac::xbox::PhotoDiscSnapshot& snapshot) { snapshot.status = "decode-error: " + message; snapshot.current_ready = false; }); if (m_log) m_log("AC_NATIVE_DISC_DECODE_ERROR " + message); } });}
void PhotoDiscService::copy_all() { bool expected = false; if (!m_copyInFlight.compare_exchange_strong(expected, true)) return; auto files = std::make_shared<std::vector<StorageFile^>>(); { std::lock_guard<std::mutex> lock(m_filesMutex); *files = m_files; } if (files->empty()) { update([](ac::xbox::PhotoDiscSnapshot& snapshot) { snapshot.copy_status = "no-photos"; }); m_copyInFlight = false; return; } auto copied = std::make_shared<std::atomic_size_t>(0); auto failed = std::make_shared<std::atomic_size_t>(0); update([](ac::xbox::PhotoDiscSnapshot& snapshot) { snapshot.copy_status = "copying"; snapshot.copied = 0; snapshot.copy_failed = 0; }); create_task(ApplicationData::Current->LocalFolder->CreateFolderAsync( L"photo-cd", CreationCollisionOption::OpenIfExists)).then( [this, files, copied, failed](StorageFolder^ destination) { task<void> chain = task_from_result(); for (std::size_t index = 0; index < files->size(); ++index) { auto file = (*files)[index]; chain = chain.then([this, destination, file, index, copied, failed]() { std::wstring name = file && file->Name ? file->Name->Data() : L"photo"; if (name.size() > 220) name.resize(220); wchar_t prefix[24]{}; swprintf_s(prefix, L"%05llu-", static_cast<unsigned long long>(index + 1)); name.insert(0, prefix); return create_task(file->CopyAsync(destination, ref new String(name.c_str()), NameCollisionOption::ReplaceExisting)).then( [this, copied, failed](task<StorageFile^> completed) { try { completed.get(); ++(*copied); } catch (...) { ++(*failed); } update([copied, failed](ac::xbox::PhotoDiscSnapshot& snapshot) { snapshot.copied = copied->load(); snapshot.copy_failed = failed->load(); }); }); }); } return chain; }).then([this, files, copied, failed](task<void> completed) { std::string error; try { completed.get(); } catch (Exception^ value) { error = clean_error(value->Message); } catch (const std::exception& value) { error = value.what(); } update([&error, copied, failed](ac::xbox::PhotoDiscSnapshot& snapshot) { snapshot.copy_status = error.empty() ? "complete" : "error: " + error; snapshot.copied = copied->load(); snapshot.copy_failed = failed->load(); }); if (m_log) m_log("AC_NATIVE_DISC_COPY copied=" + std::to_string(copied->load()) + " failed=" + std::to_string(failed->load()) + " total=" + std::to_string(files->size()) + (error.empty() ? "" : " error=" + error)); m_copyInFlight = false; });}
} // namespace NativeBios