Mercurial > foo_out_sdl
diff foosdk/sdk/foobar2000/foo_sample/IO.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/foosdk/sdk/foobar2000/foo_sample/IO.cpp Mon Jan 05 02:15:46 2026 -0500 @@ -0,0 +1,343 @@ +#include "stdafx.h" +#include <helpers/file_list_helper.h> +#include <helpers/filetimetools.h> +#include <memory> + +void RunIOTest() { + try { + auto request = http_client::get()->create_request("GET"); + request->run("https://www.foobar2000.org", fb2k::noAbort); + } catch (std::exception const & e) { + popup_message::g_show( PFC_string_formatter() << "Network test failure:\n" << e, "Information"); + return; + } + popup_message::g_show(PFC_string_formatter() << "Network test OK", "Information"); +} + +namespace { // anon namespace local classes for good measure + class tpc_copyFiles : public threaded_process_callback { + public: + tpc_copyFiles ( metadb_handle_list_cref items, const char * pathTo ) : m_pathTo(pathTo), m_outFS(filesystem::get(pathTo)) { + m_lstFiles.init_from_list( items ); + } + + void on_init(ctx_t p_wnd) override { + // Main thread, called before run() gets started + } + void run(threaded_process_status & p_status, abort_callback & p_abort) override { + // Worker thread + for( size_t fileWalk = 0; fileWalk < m_lstFiles.get_size(); ++ fileWalk ) { + + // always do this in every timeconsuming loop + // will throw exception_aborted if the user pushed 'cancel' on us + p_abort.check(); + + const char * inPath = m_lstFiles[fileWalk]; + p_status.set_progress(fileWalk, m_lstFiles.get_size()); + p_status.set_item_path( inPath ); + + try { + workWithFile(inPath, p_abort); + } catch(exception_aborted) { + // User abort, just bail + throw; + } catch(std::exception const & e) { + m_errorLog << "Could not copy: " << file_path_display(inPath) << ", reason: " << e << "\n"; + } + + } + } + + void workWithFile( const char * inPath, abort_callback & abort ) { + FB2K_console_formatter() << "File: " << file_path_display(inPath); + + // Filesystem API for inPath + const filesystem::ptr inFS = filesystem::get( inPath ); + pfc::string8 inFN; + // Extract filename+extension according to this filesystem's rules + // If it's HTTP or so, there might be ?key=value that needs stripping + inFS->extract_filename_ext(inPath, inFN); + + pfc::string8 outPath = m_pathTo; + // Suffix with outFS path separator. On Windows local filesystem this is always a backslash. + outPath.end_with( m_outFS->pathSeparator() ); + outPath += inFN; + + const double openTimeout = 1.0; // wait and keep retrying for up to 1 second in case of a sharing violation error + + // Not strictly needed, but we do it anyway + // Acquire a read lock on the file, so anyone trying to acquire a write lock will just wait till we have finished + auto inLock = file_lock_manager::get()->acquire_read( inPath, abort ); + + auto inFile = inFS->openRead( inPath, abort, openTimeout ); + + // Let's toy around with inFile + { + auto stats = inFile->get_stats(abort); + + if ( stats.m_size != filesize_invalid ) { + FB2K_console_formatter() << "Size: " << pfc::format_file_size_short(stats.m_size); + } + if ( stats.m_timestamp != filetimestamp_invalid ) { + FB2K_console_formatter() << "Last modified: " << format_filetimestamp(stats.m_timestamp); + } + pfc::string8 contentType; + if ( inFile->get_content_type( contentType ) ) { + FB2K_console_formatter() << "Content type: " << contentType; + } + + uint8_t buffer[256]; + size_t got = inFile->read(buffer, sizeof(buffer), abort); + + if ( got > 0 ) { + FB2K_console_formatter() << "Header bytes: " << pfc::format_hexdump( buffer, got ); + } + + if ( inFile->is_remote() ) { + FB2K_console_formatter() << "File is remote"; + } else { + FB2K_console_formatter() << "File is local"; + } + + // For seekable files, reopen() seeks to the beginning. + // For nonseekable stream, reopen() restarts reading the stream. + inFile->reopen(abort); + } + + // This is a glorified strcmp() for file paths. + if ( metadb::path_compare( inPath, outPath ) == 0 ) { + // Same path, go no further. Specifically don't attempt acquiring a writelock because that will never complete, unless user aborted. + FB2K_console_formatter() << "Input and output paths are the same - not copying!"; + return; + } + + // Required to write to files being currently played. + // See file_lock_manager documentation for details. + auto outLock = file_lock_manager::get()->acquire_write( outPath, abort ); + + // WARNING : if a file exists at outPath prior to this, it will be reset to zero bytes (win32 CREATE_ALWAYS semantics) + auto outFile = m_outFS->openWriteNew(outPath, abort, openTimeout); + + + + try { + // Refer to g_transfer_file implementation details in the SDK for lowlevel reading & writing details + file::g_transfer_file(inFile, outFile, abort); + } catch(...) { + + if ( inFile->is_remote() ) { + // Remote file was being downloaded? Suppress deletion of incomplete output + throw; + } + // Failed for some reason + // Release our destination file hadnle + outFile.release(); + + // .. and delete the incomplete file + try { + auto & noAbort = fb2k::noAbort; // we might be being aborted, don't let that prevent deletion + m_outFS->remove( outPath, noAbort ); + } catch(...) { + // disregard errors - just report original copy error + } + + throw; // rethrow the original copy error + } + + } + + void on_done(ctx_t p_wnd, bool p_was_aborted) override { + // All done, main thread again + + if (! p_was_aborted && m_errorLog.length() > 0 ) { + popup_message::g_show(m_errorLog, "Information"); + } + + } + + + + // This is a helper class that generates a sorted list of unique file paths in this metadb_handle_list. + // The metadb_handle_list might contain duplicate tracks or multiple subsongs in the same file. m_lstFiles will list each file only once. + file_list_helper::file_list_from_metadb_handle_list m_lstFiles; + + // Destination path + const pfc::string8 m_pathTo; + // Destination filesystem API. Obtained via filesystem::get() with destination path. + const filesystem::ptr m_outFS; + + // Error log + pfc::string_formatter m_errorLog; + }; +} + +void RunCopyFilesHere(metadb_handle_list_cref data, const char * copyTo, fb2k::hwnd_t wndParent) { + + // Create worker object, a threaded_process_callback implementation. + auto worker = fb2k::service_new<tpc_copyFiles>(data, copyTo); + const uint32_t flags = threaded_process::flag_show_abort | threaded_process::flag_show_progress | threaded_process::flag_show_item; + // Start the process asynchronously. + threaded_process::get()->run_modeless( worker, flags, wndParent, "Sample Component: Copying Files" ); + + // Our worker is now running. +} +void RunCopyFiles(metadb_handle_list_cref data) { + +#ifdef _WIN32 + // Detect modal dialog wars. + // If another modal dialog is active, bump it instead of allowing our modal dialog (uBrowseForFolder) to run. + // Suppress this if the relevant code is intended to be launched by a modal dialog. + if (!ModalDialogPrologue()) return; + + const HWND wndParent = core_api::get_main_window(); + pfc::string8 copyTo; + // shared.dll method + if (!uBrowseForFolder( wndParent, "Choose destination folder", copyTo )) return; + + // shared.dll methods are win32 API wrappers and return plain paths with no protocol prepended + // Prefix with file:// before passing to fb2k filesystem methods. + // Actually the standard fb2k filesystem implementation recognizes paths even without the prefix, but we enforce it here as a good practice. + pfc::string8 copyTo2 = PFC_string_formatter() << "file://" << copyTo; + + RunCopyFilesHere(data, copyTo2, wndParent); +#else + auto tracksCopy = std::make_shared<metadb_handle_list>( data ); + auto setup = fb2k::fileDialog::get()->setupOpenFolder(); + setup->setTitle("Choose destination folder"); + setup->runSimple( [tracksCopy] (fb2k::stringRef path) { + RunCopyFilesHere(*tracksCopy, path->c_str(), core_api::get_main_window()); + } ); + +#endif +} + + +namespace { + class processLLtags : public threaded_process_callback { + public: + processLLtags( metadb_handle_list_cref data ) : m_items(data) { + m_hints = metadb_io_v2::get()->create_hint_list(); + } + void on_init(ctx_t p_wnd) override { + // Main thread, called before run() gets started + } + void run(threaded_process_status & p_status, abort_callback & p_abort) override { + // Worker thread + + // Note: + // We should look for references to the same file (such as multiple subsongs) in the track list and update each file only once. + // But for the sake of simplicity we don't do this in a sample component. + for( size_t itemWalk = 0; itemWalk < m_items.get_size(); ++ itemWalk ) { + + // always do this in every timeconsuming loop + // will throw exception_aborted if the user pushed 'cancel' on us + p_abort.check(); + + auto item = m_items[ itemWalk ]; + p_status.set_progress( itemWalk, m_items.get_size() ); + p_status.set_item_path( item->get_path() ); + + try { + workWithTrack(item, p_abort); + } catch(exception_aborted) { + // User abort, just bail + throw; + } catch(std::exception const & e) { + m_errorLog << "Could not update: " << item << ", reason: " << e << "\n"; + } + } + } + void on_done(ctx_t p_wnd, bool p_was_aborted) override { + // All done, main thread again + if ( m_hints.is_valid() ) { + // This is the proper time to finalize the hint list + // All playlists showing these files and such will now be refreshed + m_hints->on_done(); + } + + if (!p_was_aborted && m_errorLog.length() > 0) { + popup_message::g_show(m_errorLog, "Information"); + } + } + + private: + void workWithTrack( metadb_handle_ptr item, abort_callback & abort ) { + + FB2K_console_formatter() << "foo_sample will update tags on: " << item; + + const auto subsong = item->get_subsong_index(); + const auto path = item->get_path(); + + const double openTimeout = 1.0; // wait and keep retrying for up to 1 second in case of a sharing violation error + + // Required to write to files being currently played. + // See file_lock_manager documentation for details. + auto lock = file_lock_manager::get()->acquire_write( path, abort ); + + input_info_writer::ptr writer; + input_entry::g_open_for_info_write_timeout(writer, nullptr, path, abort, openTimeout ); + + { // let's toy around with info that the writer can hand to us + auto stats = writer->get_file_stats( abort ); + if (stats.m_timestamp != filetimestamp_invalid) { + FB2K_console_formatter() << "Last-modified before tag update: " << format_filetimestamp_utc(stats.m_timestamp); + } + } + + file_info_impl info; + writer->get_info( subsong, info, abort ); + + info.meta_set("comment", "foo_sample lowlevel tags write demo was here"); + + // Touchy subject + // Should we let the user abort an incomplete tag write? + // Let's better not + auto & noAbort = fb2k::noAbort; + + // This can be called many times for files with multiple subsongs + writer->set_info( subsong, info, noAbort ); + + // This is called once - when we're done set_info()'ing + writer->commit( noAbort ); + + { // let's toy around with info that the writer can hand to us + auto stats = writer->get_file_stats(abort); + if (stats.m_timestamp != filetimestamp_invalid) { + FB2K_console_formatter() << "Last-modified after tag update: " << format_filetimestamp_utc(stats.m_timestamp); + } + } + + // Now send new info to metadb + // If we don't do this, old info may still be shown in playlists etc. + if ( true ) { + // Method #1: feed altered info directly to the hintlist. + // Makes sense here as we updated just one subsong. + auto stats = writer->get_file_stats(abort); + m_hints->add_hint(item, info, stats, true); + } else { + // Method #2: let metadb talk to our writer object (more commonly used). + // The writer is a subclass of input_info_reader and can therefore be legitimately fed to add_hint_reader() + // This will read info from all subsongs in the file and update metadb if appropriate. + m_hints->add_hint_reader(path, writer, abort); + } + } + metadb_hint_list::ptr m_hints; + const metadb_handle_list m_items; + pfc::string_formatter m_errorLog; + }; +} +void RunAlterTagsLL(metadb_handle_list_cref data) { + + const auto wndParent = core_api::get_main_window(); + + // Our worker object, a threaded_process_callback subclass. + auto worker = fb2k::service_new< processLLtags > ( data ); + + const uint32_t flags = threaded_process::flag_show_abort | threaded_process::flag_show_progress | threaded_process::flag_show_item; + + // Start the worker asynchronously. + threaded_process::get()->run_modeless(worker, flags, wndParent, "Sample Component: Updating Tags"); + + // The worker is now running. + +}
