diff --git a/columns_ui-sdk b/columns_ui-sdk index 23e0c13..43275b6 160000 --- a/columns_ui-sdk +++ b/columns_ui-sdk @@ -1 +1 @@ -Subproject commit 23e0c13caf912c35adb830cc869d0aaa974b2743 +Subproject commit 43275b6d83c618caae37a7bb76f90a2ce36ca795 diff --git a/foo_dop/bplist.h b/foo_dop/bplist.h index 5f05549..5f2c12b 100644 --- a/foo_dop/bplist.h +++ b/foo_dop/bplist.h @@ -233,7 +233,7 @@ namespace bplist size = pfc::downcast_guarded(reader.read_sized_int_bendian(1<< (temp&0x0f), m_abort)); } t_size i; - object->m_array.set_count(size); + object->m_array.resize(size); for (i=0; im_integer); return b_ret; } + + const pfc::string_simple_t& object_t::dictionary::get_child_string_strict(const wchar_t * key) + { + return get_child_strict(key); + } + + bool object_t::dictionary::get_child_bool_strict(const wchar_t * key, std::optional default_value) + { + return get_child_strict_with_default(key, default_value); + } + + int64_t object_t::dictionary::get_child_int64_strict(const wchar_t * key, std::optional default_value) + { + return get_child_strict_with_default(key, default_value); + } + + const std::vector& object_t::dictionary::get_child_array_strict(const wchar_t * key) + { + return get_child_strict(key); + } + + void object_t::dictionary::ensure_sorted() { if (!m_sorted) @@ -95,7 +117,7 @@ namespace cfobject case kTagArray: { p_out << "{\r\n"; - for (t_size i = 0, count = ptr->m_array.get_count(); im_array.size(); im_array[i], p_out); p_out << "\r\n"; @@ -153,7 +175,7 @@ namespace cfobject case kTagArray: { p_out << "\n"; - for (t_size i = 0, count = ptr->m_array.get_count(); im_array.size(); im_array[i], p_out); } @@ -198,6 +220,7 @@ namespace cfobject }; } } + void g_export_object_to_xml (const object_t::ptr_t & ptr, pfc::string8 & p_out) { p_out.reset(); diff --git a/foo_dop/cfobject.h b/foo_dop/cfobject.h index 353b8c4..e8afae0 100644 --- a/foo_dop/cfobject.h +++ b/foo_dop/cfobject.h @@ -20,6 +20,37 @@ namespace cfobject public: typedef pfc::refcounted_object_ptr_t ptr_t; + template + auto& get_value_strict() + { + if (m_type != TypeTag) { + throw exception_io_unsupported_format(); + } + + if constexpr (TypeTag == kTagInt) + return m_integer; + + if constexpr (TypeTag == kTagArray) + return m_array; + + if constexpr (TypeTag == kTagBoolean) + return m_boolean; + + if constexpr (TypeTag == kTagDictionary) + return m_dictionary; + + if constexpr (TypeTag == kTagUnicodeString) + return m_string; + + if constexpr (TypeTag == kTagData) + return m_data; + + if constexpr (TypeTag == kTagReal) + return m_float; + + throw pfc::exception_bug_check(); + } + class dictionary_entry_t { public: @@ -48,6 +79,48 @@ namespace cfobject bool get_child (const wchar_t * key, t_uint64 & p_value); bool get_child (const wchar_t * key, t_int64 & p_value); + template + auto* get_child(const wchar_t* key) + { + object_t::ptr_t child; + if (get_child(key, child)) + return &child->get_value_strict(); + + using ValuePtr = decltype(&child->get_value_strict()); + return static_cast(nullptr); + } + + /** Note: This will copy the value (unlike get_child above) */ + template + auto get_child_strict_with_default(const wchar_t* key, Default&& default_value = {}) + { + using Type = std::remove_reference_t().get_value_strict())>; + std::optional default_value_as_optional{ std::forward(default_value) }; + auto child = get_child(key); + + if (!child && !default_value_as_optional) + throw exception_io_unsupported_format(); + + if (!child) + return default_value_as_optional.value(); + + return *child; + } + + template + auto& get_child_strict(const wchar_t* key) + { + auto child = get_child(key); + if (!child) + throw exception_io_unsupported_format(); + return *child; + } + + const pfc::string_simple_t& get_child_string_strict(const wchar_t * key); + bool get_child_bool_strict(const wchar_t * key, std::optional default_value = {}); + int64_t get_child_int64_strict(const wchar_t * key, std::optional default_value = {}); + const std::vector& get_child_array_strict(const wchar_t * key); + dictionary() : m_sorted(false) {}; private: void ensure_sorted(); @@ -59,11 +132,11 @@ namespace cfobject bool m_boolean; t_int64 m_integer; double m_float; - pfc::string_simple_t m_string; - pfc::string_simple_t m_key; - pfc::list_t m_array; + pfc::string_simple_t m_string; + pfc::string_simple_t m_key; + std::vector m_array; dictionary m_dictionary; - pfc::array_t m_data; + pfc::array_t m_data; t_filetimestamp m_date; bool get_bool() {return m_boolean || m_integer;} diff --git a/foo_dop/foo_dop.vcxproj b/foo_dop/foo_dop.vcxproj index a88bc07..1ca77e3 100644 --- a/foo_dop/foo_dop.vcxproj +++ b/foo_dop/foo_dop.vcxproj @@ -225,6 +225,7 @@ + diff --git a/foo_dop/foo_dop.vcxproj.filters b/foo_dop/foo_dop.vcxproj.filters index 72adc98..ca3f886 100644 --- a/foo_dop/foo_dop.vcxproj.filters +++ b/foo_dop/foo_dop.vcxproj.filters @@ -407,6 +407,9 @@ Component + + Backend Operations + diff --git a/foo_dop/itunesdb.h b/foo_dop/itunesdb.h index 003d42c..8004729 100644 --- a/foo_dop/itunesdb.h +++ b/foo_dop/itunesdb.h @@ -20,6 +20,7 @@ t_uint32 apple_time_from_filetime(t_filetimestamp filetime, bool b_local = true); t_filetimestamp filetime_time_from_appletime(t_uint32 appletime, bool b_convert_to_utc = true); +t_uint32 current_hfs_plus_timestamp(); bool g_print_meta(const file_info & info, const char * field, pfc::string_base & p_out); bool g_print_meta_noblanks(const file_info & info, const char * field, pfc::string_base & p_out); diff --git a/foo_dop/itunesdb_helpers.cpp b/foo_dop/itunesdb_helpers.cpp index a37c346..95ffdcf 100644 --- a/foo_dop/itunesdb_helpers.cpp +++ b/foo_dop/itunesdb_helpers.cpp @@ -2,6 +2,7 @@ #include "ipod_manager.h" #include "writer_sort_helpers.h" +#include "file_adder.h" bool is_blank(const char * str) { @@ -113,6 +114,13 @@ LocalFileTimeToFileTime2( return SystemTimeToFileTime(&stUTC, lpFileTime); } +t_uint32 current_hfs_plus_timestamp() +{ + FILETIME ft{}; + GetSystemTimeAsFileTime(&ft); + return apple_time_from_filetime(g_filetime_to_timestamp(&ft)); +} + t_uint32 apple_time_from_filetime(t_filetimestamp filetime_src, bool b_local) { if (filetime_src == filetimestamp_invalid) return 0; diff --git a/foo_dop/itunesdb_playlist.cpp b/foo_dop/itunesdb_playlist.cpp index b685045..d653b49 100644 --- a/foo_dop/itunesdb_playlist.cpp +++ b/foo_dop/itunesdb_playlist.cpp @@ -161,6 +161,7 @@ namespace itunesdb { p_pihm.read_lendian_auto_t(p_out->items[i].unk1, p_abort); //36 p_pihm.read_lendian_auto_t(p_out->items[i].unk2, p_abort); //40 p_pihm.read_lendian_auto_t(p_out->items[i].item_pid, p_abort); //44 + // some 64-bit other ID at +60? } catch (exception_io_data_truncation const &) {}; diff --git a/foo_dop/mobile_device_cfobject.cpp b/foo_dop/mobile_device_cfobject.cpp index 47e11a7..2be3310 100644 --- a/foo_dop/mobile_device_cfobject.cpp +++ b/foo_dop/mobile_device_cfobject.cpp @@ -66,7 +66,7 @@ bool g_get_CFType_object (const in_mobile_device_api_handle_sync & api, CFTypeRe p_out->m_type = cfobject::kTagArray; CFArrayRef arr = (CFArrayRef)ref; t_size i, count = api->CFArrayGetCount(arr); - p_out->m_array.set_size(count); + p_out->m_array.resize(count); for (i=0; iCFArrayGetValueAtIndex(arr, i); @@ -121,7 +121,7 @@ void g_get_sql_commands (cfobject::object_t::ptr_t const & cfobj, pfc::string_li if (CommandSet.is_valid() && CommandSet->m_dictionary.get_child(L"Commands", Commands)) { - t_size j, count = Commands->m_array.get_count(); + t_size j, count = Commands->m_array.size(); for (j=0; jm_array[j].is_valid()) names.add_item(pfc::stringcvt::string_utf8_from_wide(Commands->m_array[j]->m_string.get_ptr())); diff --git a/foo_dop/mobile_device_v2.cpp b/foo_dop/mobile_device_v2.cpp index c937320..15118f7 100644 --- a/foo_dop/mobile_device_v2.cpp +++ b/foo_dop/mobile_device_v2.cpp @@ -234,7 +234,7 @@ void mobile_device_handle::initialise_international() m_icu_context.m_trans = lockedAPI->utrans_openU_4_0(International_NameTransform->m_string, -1, 0, 0, 0, 0, &status); lockedAPI->ucol_setAttribute_4_0(m_icu_context.m_coll, 7, 17, &status); - t_size section_count = International_SectionHeaders->m_array.get_count(); + t_size section_count = International_SectionHeaders->m_array.size(); m_icu_context.m_SortSections.set_count(section_count); t_size rollingCount = 0; for (t_size i = 0; im_array[i]->m_dictionary.get_child(L"FirstCharacterAfterLanguage", FirstCharacterAfterLanguage)) { lockedAPI.icu_get_sort_key_bound(m_icu_context.m_coll, FirstCharacterAfterLanguage->m_string.get_ptr(), -1, true, m_icu_context.m_SortSections[i].m_FirstCharacterAfterLanguageSortKey); - t_size HeaderCount = Headers->m_array.get_count(); + t_size HeaderCount = Headers->m_array.size(); m_icu_context.m_SortSections[i].m_HeaderSortKeys.set_size(HeaderCount); for (t_size j=0; jm_array.add_item(temp); + p_out->m_array.emplace_back(std::move(temp)); } } break; @@ -346,7 +346,7 @@ void XMLPlistParser::get_cfobject_for_key(const key_t & key, cfobject::object_t: void g_get_checkpoint_artwork_format_single(cfobject::object_t::ptr_t const & AlbumArt, pfc::list_t & p_out) { - t_size i, count = AlbumArt->m_array.get_count(); + t_size i, count = AlbumArt->m_array.size(); for (i=0; im_array[i].is_valid()) break; diff --git a/foo_dop/reader.cpp b/foo_dop/reader.cpp index d11eb1c..083d553 100644 --- a/foo_dop/reader.cpp +++ b/foo_dop/reader.cpp @@ -590,6 +590,7 @@ namespace ipod if (!m_writing || !p_ipod->mobile) read_playcounts(p_ipod, p_abort); + load_device_playlists(p_ipod, p_abort); p_status.checkpoint(); diff --git a/foo_dop/reader.h b/foo_dop/reader.h index 669b7a7..740259f 100644 --- a/foo_dop/reader.h +++ b/foo_dop/reader.h @@ -16,6 +16,17 @@ namespace ipod namespace tasks { + struct DevicePlaylist { + int64_t playlist_persistent_id{}; + int64_t version{}; + bool playlist_deleted{}; + int64_t saved_index{}; + pfc::string8 name; + /** Appears to be the timestamp of iTunesDB (and not iTunesCDB or an .itdb file) */ + int64_t db_timestamp_mac_os_date{}; + std::vector track_persistent_ids; + }; + class load_database_t { class portable_device_playbackdata_notifier_impl : public dop::portable_device_playbackdata_notifier_t @@ -101,7 +112,19 @@ namespace ipod void read_storepurchases(ipod_device_ptr_ref_t p_ipod, abort_callback & p_abort); void read_storepurchases(ipod_device_ptr_ref_t p_ipod, store_purchases_type_t, abort_callback & p_abort); void read_playcounts(ipod_device_ptr_ref_t p_ipod, abort_callback & p_abort); - static int g_compare_playlist_id (const pfc::rcptr_t & item1, const pfc::rcptr_t & item2) + + /** + * For, at least, the nano 7G. + */ + void load_device_playlists(ipod_device_ptr_ref_t p_ipod, abort_callback & p_abort); + + /** + * For, at least, the nano 7G. + */ + void load_device_playlist(const DevicePlaylist& playlist); + void clean_up_device_playlists(); + + static int g_compare_playlist_id (const pfc::rcptr_t & item1, const pfc::rcptr_t & item2) { return pfc::compare_t(item1->id,item2->id); } @@ -122,13 +145,19 @@ namespace ipod return pfc::compare_t(item1->pid,dbid); } - bool have_track (t_uint64 pid) + pfc::rcptr_t get_track_by_pid(t_uint64 pid) { for (t_size i = 0, count = m_tracks.get_count(); ipid == pid) return true; + if (m_tracks[i]->pid == pid) + return m_tracks[i]; } - return false; + return {}; + } + + bool have_track(t_uint64 pid) + { + return get_track_by_pid(pid).is_valid(); }; void glue_items (t_size start); @@ -399,6 +428,7 @@ namespace ipod private: bool m_failed; bool m_writing; + pfc::list_t< pfc::string8 > m_read_device_playlists; }; } } diff --git a/foo_dop/reader_playcounts.cpp b/foo_dop/reader_playcounts.cpp index 766f929..00a58ce 100644 --- a/foo_dop/reader_playcounts.cpp +++ b/foo_dop/reader_playcounts.cpp @@ -124,7 +124,7 @@ namespace ipod { if (root->m_dictionary.get_child(L"tracks", tracks)) { - t_size j, jcount = tracks->m_array.get_count(); + t_size j, jcount = tracks->m_array.size(); mobilecounts.set_size(jcount); for (j = 0; jget_value_strict(); + DevicePlaylist otg_playlist; + + otg_playlist.playlist_persistent_id = dict.get_child_int64_strict(L"playlistPersistentID"); + otg_playlist.saved_index = dict.get_child_int64_strict(L"savedIndex", 0); + otg_playlist.name = pfc::stringcvt::string_utf8_from_wide(dict.get_child_string_strict(L"name")); + otg_playlist.db_timestamp_mac_os_date = dict.get_child_int64_strict(L"dbTimestampMacOsDate"); + otg_playlist.playlist_deleted = dict.get_child_bool_strict(L"playlistDeleted", false); + auto& pid_objects = dict.get_child_array_strict(L"trackPersistentIds"); + + otg_playlist.track_persistent_ids.reserve(pid_objects.size()); + for (auto&& pid_object : pid_objects) { + otg_playlist.track_persistent_ids.emplace_back(pid_object->get_value_strict()); + } + + return otg_playlist; +} + +void load_database_t::load_device_playlists(ipod_device_ptr_ref_t p_ipod, abort_callback & p_abort) +{ + service_ptr_t p_counts_file; + + pfc::string8 database_folder; + p_ipod->get_database_path(database_folder); + pfc::string8 path = database_folder << "/iTunes/"; + pfc::list_t file_paths; + directory_callback_retrieveList callback(file_paths, true, false); + pfc::string8 canonical_path; + filesystem::g_get_canonical_path(path, canonical_path); + filesystem::g_list_directory(canonical_path, callback, p_abort); + + std::vector read_playlists; + + for (size_t i{0}; i < file_paths.get_count(); ++i) { + auto& file_path = file_paths[i]; + pfc::string_filename_ext name(file_path); + pfc::stringcvt::string_wide_from_utf8 wname(name); + + if (std::regex_search(wname.get_ptr(), file_name_regex)) { + read_playlists.emplace_back(read_plist_otg_playlist(file_path, p_abort)); + m_read_device_playlists.add_item(file_path); + } + } + + std::sort( + read_playlists.begin(), + read_playlists.end(), + [](auto&& left, auto&& right) { return left.saved_index < right.saved_index; } + ); + + for (auto&& otg_playlist : read_playlists) { + load_device_playlist(otg_playlist); + } +} + +void load_database_t::load_device_playlist(const DevicePlaylist& otg_playlist) +{ + if (otg_playlist.playlist_deleted) { + size_t playlist_index; + if (find_playlist_by_id(otg_playlist.playlist_persistent_id, playlist_index)) { + m_playlists.remove_by_idx(playlist_index); + } + return; + } + + const auto now = current_hfs_plus_timestamp(); + uint32_t index{}; + const bool is_new_playlist = !find_playlist_by_id(otg_playlist.playlist_persistent_id, index); + auto playlist = is_new_playlist ? pfc::rcnew_t() : m_playlists[index]; + playlist->id = static_cast(otg_playlist.playlist_persistent_id); + playlist->name = otg_playlist.name; + if (is_new_playlist) + playlist->timestamp = now; + playlist->date_modified = now; + + playlist->items.remove_all(); + for (auto&& pid : otg_playlist.track_persistent_ids) { + auto track = get_track_by_pid(pid); + if (track.is_empty()) + continue; + + t_playlist_entry entry; + entry.item_pid = pid; + entry.timestamp = track->dateadded; + entry.track_id = track->id; + playlist->items.add_item(entry); + } + + // In theory we would need to (re)generate VoiceOver files for playlist names, however + // those are only for iPod shuffles. + if (is_new_playlist) { + m_playlists.add_item(playlist); + } +} + +void load_database_t::clean_up_device_playlists() +{ + abort_callback_dummy aborter; + for (size_t i{0}; i < m_read_device_playlists.get_count(); ++i) { + auto&& path = m_read_device_playlists[i]; + try { + filesystem::g_remove(path, aborter); + } + catch (const exception_io& ex) { + console::formatter formatter; + formatter << "iPod manager: Failed to delete file: " << path; + } + } +} + +} // namespace ipod::tasks diff --git a/foo_dop/reader_purchases.cpp b/foo_dop/reader_purchases.cpp index 68bee2b..2145a11 100644 --- a/foo_dop/reader_purchases.cpp +++ b/foo_dop/reader_purchases.cpp @@ -42,7 +42,7 @@ namespace ipod cfobject::object_t::ptr_t assetOrdering; if (p_reader_data.m_root_object->m_dictionary.get_child(L"assetOrdering", assetOrdering)) { - for (t_size i = 0, count = assetOrdering->m_array.get_count(); im_array.size(); i +#include + #include #include #include diff --git a/foo_dop/writer.cpp b/foo_dop/writer.cpp index 3e56474..c71df00 100644 --- a/foo_dop/writer.cpp +++ b/foo_dop/writer.cpp @@ -52,6 +52,11 @@ void database_writer_t::write_artworkdb(ipod_device_ptr_ref_t p_ipod, const ipod } } +void database_writer_t::clean_up_after_write(ipod::tasks::load_database_t& p_library) +{ + p_library.clean_up_device_playlists(); +} + void database_writer_t::run(ipod_device_ptr_ref_t p_ipod, ipod::tasks::load_database_t & m_library, const t_field_mappings & p_mappings, threaded_process_v2_t & p_status,abort_callback & p_abort) { //profiler (writer_run); @@ -139,6 +144,7 @@ void database_writer_t::run(ipod_device_ptr_ref_t p_ipod, ipod::tasks::load_data p_status.update_progress_subpart_helper(14 + (p_ipod->m_device_properties.m_SQLiteDB?15:0),15 + (p_ipod->m_device_properties.m_SQLiteDB?15:0)); m_library.save_cache(p_status.get_wnd(), p_ipod, p_status, p_abort); + clean_up_after_write(m_library); p_status.update_progress_subpart_helper(15,15); diff --git a/foo_dop/writer.h b/foo_dop/writer.h index 95ae82a..9da5bf8 100644 --- a/foo_dop/writer.h +++ b/foo_dop/writer.h @@ -183,6 +183,7 @@ namespace ipod void write_artworkdb (ipod_device_ptr_ref_t p_ipod, const ipod::tasks::load_database_t & p_library, threaded_process_v2_t & p_status,abort_callback & p_abort); void write_sqlitedb (ipod_device_ptr_ref_t p_ipod, ipod::tasks::load_database_t & p_library, const t_field_mappings & p_mappings, threaded_process_v2_t & p_status,abort_callback & p_abort); pfc::array_staticsize_t calculate_cbk(ipod_device_ptr_ref_t p_ipod, const char* locations_itdb_path); + void clean_up_after_write(ipod::tasks::load_database_t & p_library); //construct from main thread only database_writer_t() { diff --git a/foobar2000 b/foobar2000 index 8dbdecc..923ea1e 160000 --- a/foobar2000 +++ b/foobar2000 @@ -1 +1 @@ -Subproject commit 8dbdecc47fdc7e28421c2094178c0d6dea05f6e1 +Subproject commit 923ea1ebd4266550c7e2673dcd6458ad42e320ed