File indexing completed on 2025-03-09 04:24:22
0001 /**************************************************************************************** 0002 * Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> * 0003 * * 0004 * This program is free software; you can redistribute it and/or modify it under * 0005 * the terms of the GNU General Public License as published by the Free Software * 0006 * Foundation; either version 2 of the License, or (at your option) any later * 0007 * version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0010 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0011 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0012 * * 0013 * You should have received a copy of the GNU General Public License along with * 0014 * this program. If not, see <http://www.gnu.org/licenses/>. * 0015 ****************************************************************************************/ 0016 0017 #include "IpodDeviceHelper.h" 0018 0019 #include "core/support/Debug.h" 0020 0021 #include <QDialogButtonBox> 0022 #include <QFile> 0023 #include <QFileInfo> 0024 0025 #include <KConfigGroup> 0026 #include <KFormat> 0027 #include <KLocalizedString> 0028 0029 0030 Itdb_iTunesDB* 0031 IpodDeviceHelper::parseItdb( const QString &mountPoint, QString &errorMsg ) 0032 { 0033 Itdb_iTunesDB *itdb; 0034 GError *error = nullptr; 0035 0036 errorMsg.clear(); 0037 itdb = itdb_parse( QFile::encodeName( mountPoint ), &error ); 0038 if( error ) 0039 { 0040 if( itdb ) 0041 itdb_free( itdb ); 0042 itdb = nullptr; 0043 errorMsg = QString::fromUtf8( error->message ); 0044 g_error_free( error ); 0045 error = nullptr; 0046 } 0047 if( !itdb && errorMsg.isEmpty() ) 0048 errorMsg = i18n( "Cannot parse iTunes database due to an unreported error." ); 0049 return itdb; 0050 } 0051 0052 QString 0053 IpodDeviceHelper::collectionName( Itdb_iTunesDB *itdb ) 0054 { 0055 const Itdb_IpodInfo *info = (itdb && itdb->device) ? itdb_device_get_ipod_info( itdb->device ) : nullptr; 0056 QString modelName = info ? QString::fromUtf8( itdb_info_get_ipod_model_name_string( info->ipod_model ) ) 0057 : i18nc( "iPod model that is not (yet) recognized", "Unrecognized model" ); 0058 0059 return i18nc( "Name of the iPod collection; %1 is iPod name, %2 is iPod model; example: My iPod: Nano (Blue)", 0060 "%1: %2", IpodDeviceHelper::ipodName( itdb ), modelName ); 0061 } 0062 0063 QString 0064 IpodDeviceHelper::ipodName( Itdb_iTunesDB *itdb ) 0065 { 0066 Itdb_Playlist *mpl = itdb ? itdb_playlist_mpl( itdb ) : nullptr; 0067 QString mplName = mpl ? QString::fromUtf8( mpl->name ) : QString(); 0068 if( mplName.isEmpty() ) 0069 mplName = i18nc( "default iPod name (when user-set name is empty)", "iPod" ); 0070 0071 return mplName; 0072 } 0073 0074 void 0075 IpodDeviceHelper::unlinkPlaylistsTracksFromItdb( Itdb_iTunesDB *itdb ) 0076 { 0077 if( !itdb ) 0078 return; 0079 0080 while( itdb->playlists ) 0081 { 0082 Itdb_Playlist *ipodPlaylist = (Itdb_Playlist *) itdb->playlists->data; 0083 if( !ipodPlaylist || ipodPlaylist->itdb != itdb ) 0084 { 0085 /* a) itdb_playlist_unlink() cannot work if ipodPlaylist is null, prevent 0086 * infinite loop 0087 * b) if ipodPlaylist->itdb != itdb, something went horribly wrong. Prevent 0088 * infinite loop even in this case 0089 */ 0090 itdb->playlists = g_list_remove( itdb->playlists, ipodPlaylist ); 0091 continue; 0092 } 0093 itdb_playlist_unlink( ipodPlaylist ); 0094 } 0095 0096 while( itdb->tracks ) 0097 { 0098 Itdb_Track *ipodTrack = (Itdb_Track *) itdb->tracks->data; 0099 if( !ipodTrack || ipodTrack->itdb != itdb ) 0100 { 0101 /* a) itdb_track_unlink() cannot work if ipodTrack is null, prevent infinite 0102 * loop 0103 * b) if ipodTrack->itdb != itdb, something went horribly wrong. Prevent 0104 * infinite loop even in this case 0105 */ 0106 itdb->tracks = g_list_remove( itdb->tracks, ipodTrack ); 0107 continue; 0108 } 0109 itdb_track_unlink( ipodTrack ); 0110 } 0111 } 0112 0113 /** 0114 * Return ipod info if iPod model is recognized, returns null if itdb is null or if iPod 0115 * is invalid or unknown. 0116 */ 0117 static const Itdb_IpodInfo *getIpodInfo( const Itdb_iTunesDB *itdb ) 0118 { 0119 if( !itdb || !itdb->device ) 0120 return nullptr; 0121 const Itdb_IpodInfo *info = itdb_device_get_ipod_info( itdb->device ); 0122 if( !info ) 0123 return nullptr; 0124 if( info->ipod_model == ITDB_IPOD_MODEL_INVALID 0125 || info->ipod_model == ITDB_IPOD_MODEL_UNKNOWN ) 0126 { 0127 return nullptr; 0128 } 0129 return info; 0130 } 0131 0132 static bool 0133 firewireGuidNeeded( const Itdb_IpodGeneration &generation ) 0134 { 0135 switch( generation ) 0136 { 0137 // taken from libgpod itdb_device.c itdb_device_get_checksum_type() 0138 // not nice, but should not change, no new devices use hash58 0139 case ITDB_IPOD_GENERATION_CLASSIC_1: 0140 case ITDB_IPOD_GENERATION_CLASSIC_2: 0141 case ITDB_IPOD_GENERATION_CLASSIC_3: 0142 case ITDB_IPOD_GENERATION_NANO_3: 0143 case ITDB_IPOD_GENERATION_NANO_4: 0144 return true; // ITDB_CHECKSUM_HASH58 0145 default: 0146 break; 0147 } 0148 return false; 0149 } 0150 0151 static bool 0152 hashInfoNeeded( const Itdb_IpodGeneration &generation ) 0153 { 0154 switch( generation ) 0155 { 0156 // taken from libgpod itdb_device.c itdb_device_get_checksum_type() 0157 // not nice, but should not change, current devices need libhashab 0158 case ITDB_IPOD_GENERATION_NANO_5: 0159 case ITDB_IPOD_GENERATION_TOUCH_1: 0160 case ITDB_IPOD_GENERATION_TOUCH_2: 0161 case ITDB_IPOD_GENERATION_TOUCH_3: 0162 case ITDB_IPOD_GENERATION_IPHONE_1: 0163 case ITDB_IPOD_GENERATION_IPHONE_2: 0164 case ITDB_IPOD_GENERATION_IPHONE_3: 0165 return true; // ITDB_CHECKSUM_HASH72 0166 default: 0167 break; 0168 } 0169 return false; 0170 } 0171 0172 static bool 0173 hashAbNeeded( const Itdb_IpodGeneration &generation ) 0174 { 0175 switch( generation ) 0176 { 0177 // taken from libgpod itdb_device.c itdb_device_get_checksum_type() 0178 // TODO: not nice, new released devices may be added! 0179 case ITDB_IPOD_GENERATION_IPAD_1: 0180 case ITDB_IPOD_GENERATION_IPHONE_4: 0181 case ITDB_IPOD_GENERATION_TOUCH_4: 0182 case ITDB_IPOD_GENERATION_NANO_6: 0183 return true; // ITDB_CHECKSUM_HASHAB 0184 default: 0185 break; 0186 } 0187 return false; 0188 } 0189 0190 /** 0191 * Returns true if file @param relFilename is found, readable and nonempty. 0192 * Searches in @param mountPoint /iPod_Control/Device/ 0193 */ 0194 static bool 0195 fileFound( const QString &mountPoint, const QString &relFilename ) 0196 { 0197 gchar *controlDir = itdb_get_device_dir( QFile::encodeName( mountPoint ) ); 0198 if( !controlDir ) 0199 return false; 0200 QString absFilename = QStringLiteral( "%1/%2" ).arg( QFile::decodeName( controlDir ), 0201 relFilename ); 0202 g_free( controlDir ); 0203 0204 QFileInfo fileInfo( absFilename ); 0205 return fileInfo.isReadable() && fileInfo.size() > 0; 0206 } 0207 0208 static bool 0209 safeToWriteWithMessage( const QString &mountPoint, const Itdb_iTunesDB *itdb, QString &message ) 0210 { 0211 const Itdb_IpodInfo *info = getIpodInfo( itdb ); // returns null on null itdb 0212 if( !info ) 0213 { 0214 message = i18n( "iPod model was not recognized." ); 0215 return false; 0216 } 0217 0218 QString gen = QString::fromUtf8( itdb_info_get_ipod_generation_string( info->ipod_generation ) ); 0219 if( firewireGuidNeeded( info->ipod_generation ) ) 0220 { 0221 // okay FireWireGUID may be in plain SysInfo, too, but it's hard to check and 0222 // error-prone so we just require SysInfoExtended which is machine-generated 0223 const QString sysInfoExtended( "SysInfoExtended" ); 0224 bool sysInfoExtendedExists = fileFound( mountPoint, sysInfoExtended ); 0225 message += ( sysInfoExtendedExists ) 0226 ? i18n( "%1 family uses %2 file to generate correct database checksum.", 0227 gen, sysInfoExtended ) 0228 : i18n( "%1 family needs %2 file to generate correct database checksum.", 0229 gen, sysInfoExtended ); 0230 if( !sysInfoExtendedExists ) 0231 return false; 0232 } 0233 if( hashInfoNeeded( info->ipod_generation ) ) 0234 { 0235 const QString hashInfo( "HashInfo" ); 0236 bool hashInfoExists = fileFound( mountPoint, hashInfo ); 0237 message += hashInfoExists 0238 ? i18n( "%1 family uses %2 file to generate correct database checksum.", 0239 gen, hashInfo ) 0240 : i18n( "%1 family needs %2 file to generate correct database checksum.", 0241 gen, hashInfo ); 0242 if( !hashInfoExists ) 0243 return false; 0244 } 0245 if( hashAbNeeded( info->ipod_generation ) ) 0246 { 0247 message += i18nc( "Do not translate hash-AB, libgpod, libhashab.so", 0248 "%1 family probably uses hash-AB to generate correct database checksum. " 0249 "libgpod (as of version 0.8.2) doesn't know how to compute it, but tries " 0250 "to dynamically load external library libhashab.so to do it.", gen 0251 ); 0252 // we don't return false, user may have hash-AB support installed 0253 } 0254 return true; 0255 } 0256 0257 static void 0258 fillInModelComboBox( QComboBox *comboBox, bool someSysInfoFound ) 0259 { 0260 if( someSysInfoFound ) 0261 { 0262 comboBox->addItem( i18n( "Autodetect (%1 file(s) present)", QString( "SysInfo") ), QString() ); 0263 comboBox->setEnabled( false ); 0264 return; 0265 } 0266 0267 const Itdb_IpodInfo *info = itdb_info_get_ipod_info_table(); 0268 if( !info ) 0269 { 0270 // this is not i18n-ed for purpose: it should never happen 0271 comboBox->addItem( QStringLiteral( "Failed to get iPod info table!" ), QString() ); 0272 return; 0273 } 0274 0275 while( info->model_number ) 0276 { 0277 QString generation = QString::fromUtf8( itdb_info_get_ipod_generation_string( info->ipod_generation) ); 0278 QString capacity = KFormat().formatByteSize( info->capacity * 1073741824.0, 0 ); 0279 QString modelName = QString::fromUtf8( itdb_info_get_ipod_model_name_string( info->ipod_model ) ); 0280 QString modelNumber = QString::fromUtf8( info->model_number ); 0281 QString label = i18nc( "Examples: " 0282 "%1: Nano with camera (5th Gen.); [generation]" 0283 "%2: 16 GiB; [capacity]" 0284 "%3: Nano (Orange); [model name]" 0285 "%4: A123 [model number]", 0286 "%1: %2 %3 [%4]", 0287 generation, capacity, modelName, modelNumber ); 0288 comboBox->addItem( label, modelNumber ); 0289 info++; // list is ended by null-filled info 0290 } 0291 comboBox->setMaxVisibleItems( 16 ); 0292 } 0293 0294 void 0295 IpodDeviceHelper::fillInConfigureDialog( QDialog *configureDialog, 0296 Ui::IpodConfiguration *configureDialogUi, 0297 const QString &mountPoint, 0298 Itdb_iTunesDB *itdb, 0299 const Transcoding::Configuration &transcodeConfig, 0300 const QString &errorMessage ) 0301 { 0302 static const QString unknown = i18nc( "Unknown iPod model, generation...", "Unknown" ); 0303 static const QString supported = i18nc( "In a dialog: Video: Supported", "Supported" ); 0304 static const QString notSupported = i18nc( "In a dialog: Video: Not supported", "Not supported" ); 0305 static const QString present = i18nc( "In a dialog: Some file: Present", "Present" ); 0306 static const QString notFound = i18nc( "In a dialog: Some file: Not found", "<b>Not found</b>" ); 0307 static const QString notNeeded = i18nc( "In a dialog: Some file: Not needed", "Not needed" ); 0308 0309 // following call accepts null itdb 0310 configureDialogUi->nameLineEdit->setText( IpodDeviceHelper::ipodName( itdb ) ); 0311 QString notes; 0312 QString warningText; 0313 QString safeToWriteMessage; 0314 bool isSafeToWrite = safeToWriteWithMessage( mountPoint, itdb, safeToWriteMessage ); 0315 bool sysInfoExtendedExists = fileFound( mountPoint, "SysInfoExtended" ); 0316 bool sysInfoExists = fileFound( mountPoint, "SysInfo" ); 0317 0318 if( itdb ) 0319 { 0320 configureDialogUi->nameLineEdit->setEnabled( isSafeToWrite ); 0321 configureDialogUi->transcodeComboBox->setEnabled( isSafeToWrite ); 0322 configureDialogUi->transcodeComboBox->fillInChoices( transcodeConfig ); 0323 configureDialogUi->modelComboLabel->setEnabled( false ); 0324 configureDialogUi->modelComboBox->setEnabled( false ); 0325 configureDialogUi->initializeLabel->setEnabled( false ); 0326 configureDialogUi->initializeButton->setEnabled( false ); 0327 if( !errorMessage.isEmpty() ) 0328 // to inform user about successful initialization. 0329 warningText = QString( "<b>%1</b>" ).arg( errorMessage ); 0330 0331 const Itdb_Device *device = itdb->device; 0332 const Itdb_IpodInfo *info = device ? itdb_device_get_ipod_info( device ) : nullptr; 0333 configureDialogUi->infoGroupBox->setEnabled( true ); 0334 configureDialogUi->modelPlaceholer->setText( info ? QString::fromUtf8( 0335 itdb_info_get_ipod_model_name_string( info->ipod_model ) ) : unknown ); 0336 configureDialogUi->generationPlaceholder->setText( info ? QString::fromUtf8( 0337 itdb_info_get_ipod_generation_string( info->ipod_generation ) ) : unknown ); 0338 configureDialogUi->videoPlaceholder->setText( device ? 0339 ( itdb_device_supports_video( device ) ? supported : notSupported ) : unknown ); 0340 configureDialogUi->albumArtworkPlaceholder->setText( device ? 0341 ( itdb_device_supports_artwork( device ) ? supported : notSupported ) : unknown ); 0342 0343 if( isSafeToWrite ) 0344 notes += safeToWriteMessage; // may be empty, doesn't hurt 0345 else 0346 { 0347 Q_ASSERT( !safeToWriteMessage.isEmpty() ); 0348 const QString link( "http://gtkpod.git.sourceforge.net/git/gitweb.cgi?p=gtkpod/libgpod;a=blob_plain;f=README.overview" ); 0349 notes += i18nc( "%1 is informational sentence giving reason", 0350 "<b>%1</b><br><br>" 0351 "As a safety measure, Amarok will <i>refuse to perform any writes</i> to " 0352 "iPod. (modifying iTunes database could make it look empty from the device " 0353 "point of view)<br>" 0354 "See <a href='%2'>README.overview</a> file from libgpod source repository " 0355 "for more information.", 0356 safeToWriteMessage, link 0357 ); 0358 } 0359 } 0360 else 0361 { 0362 configureDialogUi->nameLineEdit->setEnabled( true ); // for initialization 0363 configureDialogUi->modelComboLabel->setEnabled( true ); 0364 configureDialogUi->modelComboBox->setEnabled( true ); 0365 if( configureDialogUi->modelComboBox->count() == 0 ) 0366 fillInModelComboBox( configureDialogUi->modelComboBox, sysInfoExists || sysInfoExtendedExists ); 0367 configureDialogUi->initializeLabel->setEnabled( true ); 0368 configureDialogUi->initializeButton->setEnabled( true ); 0369 configureDialogUi->initializeButton->setIcon( QIcon::fromTheme( "task-attention" ) ); 0370 if( !errorMessage.isEmpty() ) 0371 warningText = i18n( 0372 "<b>%1</b><br><br>" 0373 "Above problem prevents Amarok from using your iPod. You can try to " 0374 "re-create critical iPod folders and files (including iTunes database) " 0375 "using the <b>%2</b> button below.<br><br> " 0376 "Initializing iPod <b>destroys iPod track and photo database</b>, however " 0377 "it should not delete any tracks. The tracks will become orphaned.", 0378 errorMessage, 0379 configureDialogUi->initializeButton->text().remove( QChar('&') ) 0380 ); 0381 0382 configureDialogUi->infoGroupBox->setEnabled( false ); 0383 configureDialogUi->modelPlaceholer->setText( unknown ); 0384 configureDialogUi->generationPlaceholder->setText( unknown ); 0385 configureDialogUi->videoPlaceholder->setText( unknown ); 0386 configureDialogUi->albumArtworkPlaceholder->setText( unknown ); 0387 } 0388 0389 if( !warningText.isEmpty() ) 0390 { 0391 configureDialogUi->initializeLabel->setText( warningText ); 0392 configureDialogUi->initializeLabel->adjustSize(); 0393 } 0394 0395 QString sysInfoExtendedString = sysInfoExtendedExists ? present : notFound; 0396 QString sysInfoString = sysInfoExists ? present : 0397 ( sysInfoExtendedExists ? notNeeded : notFound ); 0398 0399 configureDialogUi->sysInfoPlaceholder->setText( sysInfoString ); 0400 configureDialogUi->sysInfoExtendedPlaceholder->setText( sysInfoExtendedString ); 0401 configureDialogUi->notesPlaceholder->setText( notes ); 0402 configureDialogUi->notesPlaceholder->adjustSize(); 0403 0404 configureDialog->findChild<QDialogButtonBox*>()->button( QDialogButtonBox::Ok )->setEnabled( isSafeToWrite ); 0405 } 0406 0407 bool 0408 IpodDeviceHelper::initializeIpod( const QString &mountPoint, 0409 const Ui::IpodConfiguration *configureDialogUi, 0410 QString &errorMessage ) 0411 { 0412 DEBUG_BLOCK 0413 bool success = true; 0414 0415 int currentModelIndex = configureDialogUi->modelComboBox->currentIndex(); 0416 QByteArray modelNumber = configureDialogUi->modelComboBox->itemData( currentModelIndex ).toString().toUtf8(); 0417 if( !modelNumber.isEmpty() ) 0418 { 0419 modelNumber.prepend( 'x' ); // ModelNumStr should start with x 0420 const char *modelNumberRaw = modelNumber.constData(); 0421 Itdb_Device *device = itdb_device_new(); 0422 // following call reads existing SysInfo 0423 itdb_device_set_mountpoint( device, QFile::encodeName( mountPoint ) ); 0424 const char *field = "ModelNumStr"; 0425 debug() << "Setting SysInfo field" << field << "to value" << modelNumberRaw; 0426 itdb_device_set_sysinfo( device, field, modelNumberRaw ); 0427 GError *error = nullptr; 0428 success = itdb_device_write_sysinfo( device, &error ); 0429 if( !success ) 0430 { 0431 if( error ) 0432 { 0433 errorMessage = i18nc( "Do not translate SysInfo", 0434 "Failed to write SysInfo: %1", error->message ); 0435 g_error_free( error ); 0436 } 0437 else 0438 errorMessage = i18nc( "Do not translate SysInfo", 0439 "Failed to write SysInfo file due to an unreported error" ); 0440 } 0441 itdb_device_free( device ); 0442 if( !success ) 0443 return success; 0444 } 0445 0446 QString name = configureDialogUi->nameLineEdit->text(); 0447 if( name.isEmpty() ) 0448 name = ipodName( nullptr ); // return fallback name 0449 0450 GError *error = nullptr; 0451 success = itdb_init_ipod( QFile::encodeName( mountPoint ), nullptr /* model number */, 0452 name.toUtf8(), &error ); 0453 errorMessage.clear(); 0454 if( error ) 0455 { 0456 errorMessage = QString::fromUtf8( error->message ); 0457 g_error_free( error ); 0458 error = nullptr; 0459 } 0460 if( !success && errorMessage.isEmpty() ) 0461 errorMessage = i18n( "Cannot initialize iPod due to an unreported error." ); 0462 return success; 0463 } 0464 0465 void 0466 IpodDeviceHelper::setIpodName( Itdb_iTunesDB *itdb, const QString &newName ) 0467 { 0468 if( !itdb ) 0469 return; 0470 Itdb_Playlist *mpl = itdb_playlist_mpl( itdb ); 0471 if( !mpl ) 0472 return; 0473 g_free( mpl->name ); 0474 mpl->name = g_strdup( newName.toUtf8() ); 0475 } 0476 0477 bool 0478 IpodDeviceHelper::safeToWrite( const QString &mountPoint, const Itdb_iTunesDB *itdb ) 0479 { 0480 QString dummyMessage; 0481 return safeToWriteWithMessage( mountPoint, itdb, dummyMessage ); 0482 }