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