Skip to content

Commit

Permalink
Add support for nano 7G on-the-go playlists
Browse files Browse the repository at this point in the history
#6
Includes support for added, modified and removed playlists.
  • Loading branch information
reupen committed Dec 12, 2018
1 parent 61358c3 commit d25b3cf
Show file tree
Hide file tree
Showing 13 changed files with 284 additions and 4 deletions.
23 changes: 23 additions & 0 deletions foo_dop/cfobject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,28 @@ namespace cfobject
p_value = (child->m_integer);
return b_ret;
}

const pfc::string_simple_t<wchar_t>& object_t::dictionary::get_child_string_strict(const wchar_t * key)
{
return get_child_strict<objectType::kTagUnicodeString>(key);
}

bool object_t::dictionary::get_child_bool_strict(const wchar_t * key, std::optional<bool> default_value)
{
return get_child_strict_with_default<objectType::kTagBoolean>(key, default_value);
}

int64_t object_t::dictionary::get_child_int64_strict(const wchar_t * key, std::optional<int64_t> default_value)
{
return get_child_strict_with_default<objectType::kTagInt>(key, default_value);
}

const std::vector<object_t::ptr_t>& object_t::dictionary::get_child_array_strict(const wchar_t * key)
{
return get_child_strict<objectType::kTagArray>(key);
}


void object_t::dictionary::ensure_sorted()
{
if (!m_sorted)
Expand Down Expand Up @@ -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();
Expand Down
73 changes: 73 additions & 0 deletions foo_dop/cfobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,37 @@ namespace cfobject
public:
typedef pfc::refcounted_object_ptr_t<object_t> ptr_t;

template <objectType TypeTag>
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:
Expand Down Expand Up @@ -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<objectType TypeTag>
auto* get_child(const wchar_t* key)
{
object_t::ptr_t child;
if (get_child(key, child))
return &child->get_value_strict<TypeTag>();

using ValuePtr = decltype(&child->get_value_strict<TypeTag>());
return static_cast<ValuePtr>(nullptr);
}

/** Note: This will copy the value (unlike get_child above) */
template<objectType TypeTag, class Default>
auto get_child_strict_with_default(const wchar_t* key, Default&& default_value = {})
{
using Type = std::remove_reference_t<decltype(std::declval<object_t&>().get_value_strict<TypeTag>())>;
std::optional<Type> default_value_as_optional{ std::forward<Default>(default_value) };
auto child = get_child<TypeTag>(key);

if (!child && !default_value_as_optional)
throw exception_io_unsupported_format();

if (!child)
return default_value_as_optional.value();

return *child;
}

template<objectType TypeTag>
auto& get_child_strict(const wchar_t* key)
{
auto child = get_child<TypeTag>(key);
if (!child)
throw exception_io_unsupported_format();
return *child;
}

const pfc::string_simple_t<wchar_t>& get_child_string_strict(const wchar_t * key);
bool get_child_bool_strict(const wchar_t * key, std::optional<bool> default_value = {});
int64_t get_child_int64_strict(const wchar_t * key, std::optional<int64_t> default_value = {});
const std::vector<object_t::ptr_t>& get_child_array_strict(const wchar_t * key);

dictionary() : m_sorted(false) {};
private:
void ensure_sorted();
Expand Down
1 change: 1 addition & 0 deletions foo_dop/foo_dop.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@
<ClCompile Include="reader_cache.cpp" />
<ClCompile Include="reader_dopdb.cpp" />
<ClCompile Include="reader_playcounts.cpp" />
<ClCompile Include="reader_playlists.cpp" />
<ClCompile Include="reader_purchases.cpp" />
<ClCompile Include="remove_files.cpp" />
<ClCompile Include="results.cpp" />
Expand Down
3 changes: 3 additions & 0 deletions foo_dop/foo_dop.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,9 @@
<ClCompile Include="device_info.cpp">
<Filter>Component</Filter>
</ClCompile>
<ClCompile Include="reader_playlists.cpp">
<Filter>Backend Operations</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Image Include="folder.ico">
Expand Down
1 change: 1 addition & 0 deletions foo_dop/itunesdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions foo_dop/itunesdb_helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "ipod_manager.h"
#include "writer_sort_helpers.h"
#include "file_adder.h"

bool is_blank(const char * str)
{
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions foo_dop/itunesdb_playlist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 &) {};

Expand Down
1 change: 1 addition & 0 deletions foo_dop/reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
38 changes: 34 additions & 4 deletions foo_dop/reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<int64_t> track_persistent_ids;
};

class load_database_t
{
class portable_device_playbackdata_notifier_impl : public dop::portable_device_playbackdata_notifier_t
Expand Down Expand Up @@ -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<t_playlist> & item1, const pfc::rcptr_t<t_playlist> & 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<t_playlist> & item1, const pfc::rcptr_t<t_playlist> & item2)
{
return pfc::compare_t(item1->id,item2->id);
}
Expand All @@ -122,13 +145,19 @@ namespace ipod
return pfc::compare_t(item1->pid,dbid);
}

bool have_track (t_uint64 pid)
pfc::rcptr_t <t_track> get_track_by_pid(t_uint64 pid)
{
for (t_size i = 0, count = m_tracks.get_count(); i<count; i++)
{
if (m_tracks[i]->pid == 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);
Expand Down Expand Up @@ -399,6 +428,7 @@ namespace ipod
private:
bool m_failed;
bool m_writing;
pfc::list_t< pfc::string8 > m_read_device_playlists;
};
}
}
Expand Down
129 changes: 129 additions & 0 deletions foo_dop/reader_playlists.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#include "stdafx.h"

#include "plist.h"

static const std::wregex file_name_regex(L"Playlist_[A-Z0-9]{1,16}.plist");

namespace ipod::tasks
{

DevicePlaylist read_plist_otg_playlist(const char* path, abort_callback & p_abort)
{
file::ptr f;
filesystem::g_open_read(f, path, p_abort);
auto parser = PlistParserFromFile(path, p_abort);

if (parser.m_root_object.is_empty())
throw exception_io_unsupported_format();

auto& dict = parser.m_root_object->get_value_strict<cfobject::objectType::kTagDictionary>();
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<cfobject::objectType::kTagInt>());
}

return otg_playlist;
}

void load_database_t::load_device_playlists(ipod_device_ptr_ref_t p_ipod, abort_callback & p_abort)
{
service_ptr_t<file> p_counts_file;

pfc::string8 database_folder;
p_ipod->get_database_path(database_folder);
pfc::string8 path = database_folder << "/iTunes/";
pfc::list_t<pfc::string_simple> 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<DevicePlaylist> 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<itunesdb::t_playlist>() : m_playlists[index];
playlist->id = static_cast<uint64_t>(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
3 changes: 3 additions & 0 deletions foo_dop/stdafx.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
//#define LOAD_LIBRARY_INDICES
//#define PHOTO_BROWSER

#include <optional>
#include <regex>

#include <winsock2.h>
#include <ws2tcpip.h>
#include <commctrl.h>
Expand Down
Loading

0 comments on commit d25b3cf

Please sign in to comment.