1 #include <mtp/metadata/Library.h>
2 #include <mtp/ptp/Session.h>
3 #include <mtp/ptp/ObjectPropertyListParser.h>
4 #include <mtp/log.h>
5 #include <algorithm>
6 #include <unordered_map>
7 
8 namespace mtp
9 {
10 	namespace
11 	{
12 		const std::string UknownArtist		("UknownArtist");
13 		const std::string UknownAlbum		("UknownAlbum");
14 		const std::string VariousArtists	("VariousArtists");
15 	}
16 
ListAssociations(ObjectId parentId)17 	Library::NameToObjectIdMap Library::ListAssociations(ObjectId parentId)
18 	{
19 		NameToObjectIdMap list;
20 
21 		ByteArray data = _session->GetObjectPropertyList(parentId, ObjectFormat::Association, ObjectProperty::ObjectFilename, 0, 1);
22 		ObjectPropertyListParser<std::string> parser;
23 		parser.Parse(data, [&](ObjectId id, ObjectProperty property, const std::string &name) {
24 			list.insert(std::make_pair(name, id));
25 		});
26 		return list;
27 	}
28 
GetOrCreate(ObjectId parentId,const std::string & name)29 	ObjectId Library::GetOrCreate(ObjectId parentId, const std::string &name)
30 	{
31 		auto objects = _session->GetObjectHandles(_storage, mtp::ObjectFormat::Association, parentId);
32 		for (auto id : objects.ObjectHandles)
33 		{
34 			auto oname = _session->GetObjectStringProperty(id, ObjectProperty::ObjectFilename);
35 			if (name == oname)
36 				return id;
37 		}
38 		return _session->CreateDirectory(name, parentId, _storage).ObjectId;
39 	}
40 
Library(const mtp::SessionPtr & session,ProgressReporter && reporter)41 	Library::Library(const mtp::SessionPtr & session, ProgressReporter && reporter): _session(session)
42 	{
43 		auto storages = _session->GetStorageIDs();
44 		if (storages.StorageIDs.empty())
45 			throw std::runtime_error("no storages found");
46 
47 		u64 progress = 0, total = 0;
48 		if (reporter)
49 			reporter(State::Initialising, progress, total);
50 
51 		_artistSupported = _session->GetDeviceInfo().Supports(ObjectFormat::Artist);
52 		debug("device supports ObjectFormat::Artist: ", _artistSupported? "yes": "no");
53 		{
54 			auto propsSupported = _session->GetObjectPropertiesSupported(ObjectFormat::AbstractAudioAlbum);
55 			_albumDateAuthoredSupported = std::find(propsSupported.ObjectPropertyCodes.begin(), propsSupported.ObjectPropertyCodes.end(), ObjectProperty::DateAuthored) != propsSupported.ObjectPropertyCodes.end();
56 		}
57 
58 		_storage = storages.StorageIDs[0]; //picking up first storage.
59 		//zune fails to create artist/album without storage id
60 		{
61 			ByteArray data = _session->GetObjectPropertyList(Session::Root, ObjectFormat::Association, ObjectProperty::ObjectFilename, 0, 1);
62 			ObjectStringPropertyListParser::Parse(data, [&](ObjectId id, ObjectProperty property, const std::string &name)
63 			{
64 				if (name == "Artists")
65 					_artistsFolder = id;
66 				else if (name == "Albums")
67 					_albumsFolder = id;
68 				else if (name == "Music")
69 					_musicFolder = id;
70 			});
71 		}
72 		if (_artistSupported && _artistsFolder == ObjectId())
73 			_artistsFolder = _session->CreateDirectory("Artists", Session::Root, _storage).ObjectId;
74 		if (_albumsFolder == ObjectId())
75 			_albumsFolder = _session->CreateDirectory("Albums", Session::Root, _storage).ObjectId;
76 		if (_musicFolder == ObjectId())
77 			_musicFolder = _session->CreateDirectory("Music", Session::Root, _storage).ObjectId;
78 
79 		debug("artists folder: ", _artistsFolder != ObjectId()? _artistsFolder.Id: 0);
80 		debug("albums folder: ", _albumsFolder.Id);
81 		debug("music folder: ", _musicFolder.Id);
82 
83 		auto musicFolders = ListAssociations(_musicFolder);
84 
85 		using namespace mtp;
86 
87 		ByteArray artists, albums;
88 		if (_artistSupported)
89 		{
90 			debug("getting artists...");
91 			if (reporter)
92 				reporter(State::QueryingArtists, progress, total);
93 
94 			artists = _session->GetObjectPropertyList(Session::Root, mtp::ObjectFormat::Artist, mtp::ObjectProperty::Name, 0, 1);
95 			HexDump("artists", artists);
96 
97 			total += ObjectStringPropertyListParser::GetSize(artists);
98 		}
99 		{
100 			debug("getting albums...");
101 			if (reporter)
102 				reporter(State::QueryingAlbums, progress, total);
103 
104 			albums = _session->GetObjectPropertyList(Session::Root, mtp::ObjectFormat::AbstractAudioAlbum,  mtp::ObjectProperty::Name, 0, 1);
105 			HexDump("albums", artists);
106 
107 			total += ObjectStringPropertyListParser::GetSize(albums);
108 		}
109 
110 		if (_artistSupported)
111 		{
112 			if (reporter)
113 				reporter(State::LoadingArtists, progress, total);
114 
115 			ObjectStringPropertyListParser::Parse(artists, [&](ObjectId id, ObjectProperty property, const std::string &name)
116 			{
117 				debug("artist: ", name, "\t", id.Id);
118 				auto artist = std::make_shared<Artist>();
119 				artist->Id = id;
120 				artist->Name = name;
121 				auto it = musicFolders.find(name);
122 				if (it != musicFolders.end())
123 					artist->MusicFolderId = it->second;
124 				else
125 					artist->MusicFolderId = _session->CreateDirectory(name, _musicFolder, _storage).ObjectId;
126 
127 				_artists.insert(std::make_pair(name, artist));
128 				if (reporter)
129 					reporter(State::LoadingArtists, ++progress, total);
130 			});
131 		}
132 
133 		if (reporter)
134 			reporter(State::LoadingAlbums, progress, total);
135 
136 		std::unordered_map<ArtistPtr, NameToObjectIdMap> albumFolders;
137 		ObjectStringPropertyListParser::Parse(albums, [&](ObjectId id, ObjectProperty property, const std::string &name)
138 		{
139 			auto artistName = _session->GetObjectStringProperty(id, ObjectProperty::Artist);
140 
141 			std::string albumDate;
142 			if (_albumDateAuthoredSupported)
143 				albumDate = _session->GetObjectStringProperty(id, ObjectProperty::DateAuthored);
144 
145 			auto artist = GetArtist(artistName);
146 			if (!artist)
147 				artist = CreateArtist(artistName);
148 
149 			debug("album: ", artistName, " -- ", name, "\t", id.Id, "\t", albumDate);
150 			auto album = std::make_shared<Album>();
151 			album->Name = name;
152 			album->Artist = artist;
153 			album->Id = id;
154 			album->Year = !albumDate.empty()? ConvertDateTime(albumDate): 0;
155 			if (albumFolders.find(artist) == albumFolders.end()) {
156 				albumFolders[artist] = ListAssociations(artist->MusicFolderId);
157 			}
158 			auto it = albumFolders.find(artist);
159 			if (it == albumFolders.end())
160 				throw std::runtime_error("no iterator after insert, internal error");
161 
162 			const auto & albums = it->second;
163 			auto alit = albums.find(name);
164 			if (alit != albums.end())
165 				album->MusicFolderId = alit->second;
166 			else
167 				album->MusicFolderId = _session->CreateDirectory(name, artist->MusicFolderId, _storage).ObjectId;
168 
169 			_albums.insert(std::make_pair(std::make_pair(artist, name), album));
170 			if (reporter)
171 				reporter(State::LoadingAlbums, ++progress, total);
172 		});
173 
174 		if (reporter)
175 			reporter(State::Loaded, progress, total);
176 	}
177 
~Library()178 	Library::~Library()
179 	{ }
180 
GetArtist(std::string name)181 	Library::ArtistPtr Library::GetArtist(std::string name)
182 	{
183 		if (name.empty())
184 			name = UknownArtist;
185 
186 		auto it = _artists.find(name);
187 		return it != _artists.end()? it->second: ArtistPtr();
188 	}
189 
190 
CreateArtist(std::string name)191 	Library::ArtistPtr Library::CreateArtist(std::string name)
192 	{
193 		if (name.empty())
194 			name = UknownArtist;
195 
196 		auto artist = std::make_shared<Artist>();
197 		artist->Name = name;
198 		artist->MusicFolderId = GetOrCreate(_musicFolder, name);
199 
200 		if (_artistSupported)
201 		{
202 			ByteArray propList;
203 			OutputStream os(propList);
204 
205 			os.Write32(2); //number of props
206 
207 			os.Write32(0); //object handle
208 			os.Write16(static_cast<u16>(ObjectProperty::Name));
209 			os.Write16(static_cast<u16>(DataTypeCode::String));
210 			os.WriteString(name);
211 
212 			os.Write32(0); //object handle
213 			os.Write16(static_cast<u16>(ObjectProperty::ObjectFilename));
214 			os.Write16(static_cast<u16>(DataTypeCode::String));
215 			os.WriteString(name + ".art");
216 
217 			auto response = _session->SendObjectPropList(_storage, _artistsFolder, ObjectFormat::Artist, 0, propList);
218 			artist->Id = response.ObjectId;
219 		}
220 
221 		_artists.insert(std::make_pair(name, artist));
222 		return artist;
223 	}
224 
GetAlbum(const ArtistPtr & artist,std::string name)225 	Library::AlbumPtr Library::GetAlbum(const ArtistPtr & artist, std::string name)
226 	{
227 		if (name.empty())
228 			name = UknownAlbum;
229 
230 		auto it = _albums.find(std::make_pair(artist, name));
231 		return it != _albums.end()? it->second: AlbumPtr();
232 	}
233 
CreateAlbum(const ArtistPtr & artist,std::string name,int year)234 	Library::AlbumPtr Library::CreateAlbum(const ArtistPtr & artist, std::string name, int year)
235 	{
236 		if (!artist)
237 			throw std::runtime_error("artists is required");
238 
239 		if (name.empty())
240 			name = UknownAlbum;
241 
242 		ByteArray propList;
243 		OutputStream os(propList);
244 		bool sendYear = year != 0 && _albumDateAuthoredSupported;
245 
246 		os.Write32(3 + (sendYear? 1: 0)); //number of props
247 
248 		if (_artistSupported)
249 		{
250 			os.Write32(0); //object handle
251 			os.Write16(static_cast<u16>(ObjectProperty::ArtistId));
252 			os.Write16(static_cast<u16>(DataTypeCode::Uint32));
253 			os.Write32(artist->Id.Id);
254 		}
255 		else
256 		{
257 			os.Write32(0); //object handle
258 			os.Write16(static_cast<u16>(ObjectProperty::Artist));
259 			os.Write16(static_cast<u16>(DataTypeCode::String));
260 			os.WriteString(artist->Name);
261 		}
262 
263 		os.Write32(0); //object handle
264 		os.Write16(static_cast<u16>(ObjectProperty::Name));
265 		os.Write16(static_cast<u16>(DataTypeCode::String));
266 		os.WriteString(name);
267 
268 		os.Write32(0); //object handle
269 		os.Write16(static_cast<u16>(ObjectProperty::ObjectFilename));
270 		os.Write16(static_cast<u16>(DataTypeCode::String));
271 		os.WriteString(artist->Name + "--" + name + ".alb");
272 
273 		if (sendYear)
274 		{
275 			os.Write32(0); //object handle
276 			os.Write16(static_cast<u16>(ObjectProperty::DateAuthored));
277 			os.Write16(static_cast<u16>(DataTypeCode::String));
278 			os.WriteString(ConvertYear(year));
279 		}
280 
281 		auto album = std::make_shared<Album>();
282 		album->Artist = artist;
283 		album->Name = name;
284 		album->Year = year;
285 		album->MusicFolderId = GetOrCreate(artist->MusicFolderId, name);
286 
287 		auto response = _session->SendObjectPropList(_storage, _albumsFolder, ObjectFormat::AbstractAudioAlbum, 0, propList);
288 		album->Id = response.ObjectId;
289 
290 		_albums.insert(std::make_pair(std::make_pair(artist, name), album));
291 		return album;
292 	}
293 
HasTrack(const AlbumPtr & album,const std::string & name,int trackIndex)294 	bool Library::HasTrack(const AlbumPtr & album, const std::string &name, int trackIndex)
295 	{
296 		if (!album)
297 			return false;
298 
299 		LoadRefs(album);
300 
301 		auto & tracks = album->Tracks;
302 		auto range = tracks.equal_range(name);
303 		for(auto i = range.first; i != range.second; ++i)
304 		{
305 			if (i->second == trackIndex)
306 				return true;
307 		}
308 
309 		return false;
310 	}
311 
CreateTrack(const ArtistPtr & artist,const AlbumPtr & album,ObjectFormat type,std::string name,const std::string & genre,int trackIndex,const std::string & filename,size_t size)312 	Library::NewTrackInfo Library::CreateTrack(const ArtistPtr & artist,
313 		const AlbumPtr & album,
314 		ObjectFormat type,
315 		std::string name, const std::string & genre, int trackIndex,
316 		const std::string &filename, size_t size)
317 	{
318 		ByteArray propList;
319 		OutputStream os(propList);
320 
321 		os.Write32(3 + (!genre.empty()? 1: 0) + (trackIndex? 1: 0)); //number of props
322 
323 		if (_artistSupported)
324 		{
325 			os.Write32(0); //object handle
326 			os.Write16(static_cast<u16>(ObjectProperty::ArtistId));
327 			os.Write16(static_cast<u16>(DataTypeCode::Uint32));
328 			os.Write32(artist->Id.Id);
329 		}
330 		else
331 		{
332 			os.Write32(0); //object handle
333 			os.Write16(static_cast<u16>(ObjectProperty::Artist));
334 			os.Write16(static_cast<u16>(DataTypeCode::String));
335 			os.WriteString(artist->Name);
336 		}
337 
338 
339 		os.Write32(0); //object handle
340 		os.Write16(static_cast<u16>(ObjectProperty::Name));
341 		os.Write16(static_cast<u16>(DataTypeCode::String));
342 		os.WriteString(name);
343 
344 		if (trackIndex)
345 		{
346 			os.Write32(0); //object handle
347 			os.Write16(static_cast<u16>(ObjectProperty::Track));
348 			os.Write16(static_cast<u16>(DataTypeCode::Uint16));
349 			os.Write16(trackIndex);
350 		}
351 
352 		if (!genre.empty())
353 		{
354 			os.Write32(0); //object handle
355 			os.Write16(static_cast<u16>(ObjectProperty::Genre));
356 			os.Write16(static_cast<u16>(DataTypeCode::String));
357 			os.WriteString(genre);
358 		}
359 
360 		os.Write32(0); //object handle
361 		os.Write16(static_cast<u16>(ObjectProperty::ObjectFilename));
362 		os.Write16(static_cast<u16>(DataTypeCode::String));
363 		os.WriteString(filename);
364 
365 		auto response = _session->SendObjectPropList(_storage, album->MusicFolderId, type, size, propList);
366 		NewTrackInfo ti;
367 		ti.Id = response.ObjectId;
368 		ti.Name = name;
369 		ti.Index = trackIndex;
370 		return ti;
371 	}
372 
LoadRefs(AlbumPtr album)373 	void Library::LoadRefs(AlbumPtr album)
374 	{
375 		if (!album || album->RefsLoaded)
376 			return;
377 
378 		auto refs = _session->GetObjectReferences(album->Id).ObjectHandles;
379 		std::copy(refs.begin(), refs.end(), std::inserter(album->Refs, album->Refs.begin()));
380 		for(auto trackId : refs)
381 		{
382 			auto name = _session->GetObjectStringProperty(trackId, ObjectProperty::Name);
383 			auto index = _session->GetObjectIntegerProperty(trackId, ObjectProperty::Track);
384 			debug("[", index, "]: ", name);
385 			album->Tracks.insert(std::make_pair(name, index));
386 		}
387 		album->RefsLoaded = true;
388 	}
389 
AddTrack(AlbumPtr album,const NewTrackInfo & ti)390 	void Library::AddTrack(AlbumPtr album, const NewTrackInfo & ti)
391 	{
392 		if (!album)
393 			return;
394 
395 		LoadRefs(album);
396 
397 		auto & refs = album->Refs;
398 		auto & tracks = album->Tracks;
399 
400 		msg::ObjectHandles handles;
401 		std::copy(refs.begin(), refs.end(), std::back_inserter(handles.ObjectHandles));
402 		handles.ObjectHandles.push_back(ti.Id);
403 		_session->SetObjectReferences(album->Id, handles);
404 		refs.insert(ti.Id);
405 		tracks.insert(std::make_pair(ti.Name, ti.Index));
406 	}
407 
408 
Supported(const mtp::SessionPtr & session)409 	bool Library::Supported(const mtp::SessionPtr & session)
410 	{
411 		auto & gdi = session->GetDeviceInfo();
412 		return
413 			gdi.Supports(OperationCode::GetObjectPropList) &&
414 			gdi.Supports(OperationCode::SendObjectPropList) &&
415 			gdi.Supports(OperationCode::SetObjectReferences) &&
416 			gdi.Supports(ObjectFormat::AbstractAudioAlbum);
417 		;
418 	}
419 
420 }
421