ripping.cpp

Go to the documentation of this file.
00001 /*
00002     Copyright (C) 2008  Tim Fechtner < urwald at users dot sourceforge dot net >
00003 
00004     This program is free software; you can redistribute it and/or
00005     modify it under the terms of the GNU General Public License as
00006     published by the Free Software Foundation; either version 2 of
00007     the License or (at your option) version 3 or any later version
00008     accepted by the membership of KDE e.V. (or its successor approved
00009     by the membership of KDE e.V.), which shall act as a proxy
00010     defined in Section 14 of version 3 of the license.
00011 
00012     This program is distributed in the hope that it will be useful,
00013     but WITHOUT ANY WARRANTY; without even the implied warranty of
00014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015     GNU General Public License for more details.
00016 
00017     You should have received a copy of the GNU General Public License
00018     along with this program.  If not, see <http://www.gnu.org/licenses/>.
00019 */
00020 
00021 #include "ripping.h"
00022 #include "settings_general.h"
00023 #define AND  &&
00024 #define OR  ||
00025 #define NOT  !
00026 #define EQUAL  ==
00027 
00028 #include <KGlobal>
00029 #include <KLocale>
00030 #include <QDir>
00031 #include <QFileInfo>
00032 #include "proxyinfo.h"
00033 
00034 ripping::ripping(QObject *parent) : streamripper_base(parent)
00035 {
00036   internal_splitBehavior = QString::SkipEmptyParts;  /* makes sure that
00037   interpretate_console_output() doesn't get empty lines (which
00038   wouldn't make any sense, but only consume CPU time) */
00039 
00040   /* The internal variables who hold the properties must get initialized.
00041   *
00042   *  We use resetStreamripperProperties(), which uses the resetXXX()
00043   *  functions which use the setXXX() functions.
00044   *
00045   *  The resetStreamripperProperties() function and the resetXXX()
00046   *  functions are virtual. This means they are reimplemented in
00047   *  the inherited class "radiostation" to write directly to the
00048   *  config file. But it isn't desired. Instead we need only an
00049   *  initialation of the "PropertyValue internal_XXX;" members,
00050   *  and this initialation is necessary for the inherited class
00051   *  "get_stream_info" who relays on a working (and with "unset"
00052   *  initialised) properties system.
00053   *
00054   *  However we can safly call the reset functions in the constructor,
00055   *  because during the constructor call the reimplementations of
00056   *  the virtual functions are not yet available. */
00057   resetStreamripperProperties(); // this will emit signals just for the initialatation
00058                                   //(what's undesired),
00059                                   // but that's no problem, because during this constructor
00060                                   // is running, there can't still any slot be conneted!
00061 
00062   QObject::connect(&m_process,
00063                    SIGNAL(stateChanged(QProcess::ProcessState)),
00064                    this,
00065                    SLOT(streamripperStateChange(QProcess::ProcessState)));
00066   QObject::connect(&m_process,
00067                    SIGNAL(error(QProcess::ProcessError)),
00068                    this,
00069                    SLOT(errorOccured(QProcess::ProcessError)));
00070   QObject::connect(this,
00071                    SIGNAL(bitrateChanged(qlonglong, PropertyValue)),
00072                    this,
00073                    SLOT(emit_metaInterval_milliSecondsChanged()));
00074 }
00075 
00076 ripping::~ripping()
00077 {
00078 }
00079 
00080 void ripping::emit_metaInterval_milliSecondsChanged()
00081 {
00082   emit metaInterval_milliSecondsChanged(index(), metaInterval_milliSeconds());
00083 }
00084 
00085 void ripping::resetStreamripperProperties()
00086 {
00087   setBitrate(default_value_of_bitrate());
00088   setDataSize(default_value_of_dataSize());
00089   setError(default_value_of_error());
00090   setMetaInterval(default_value_of_metaInterval());
00091   setRelayPort(default_value_of_relayPort());
00092   setServerName(default_value_of_serverName());
00093   setSong(default_value_of_song());
00094   setStatus(default_value_of_status());
00095   setStreamName(default_value_of_streamName());
00096 }
00097 
00098 void ripping::interpretate_console_output(QStringList & stringList)
00099 {
00100   // variables
00101   QString my_line;
00102   // recognized data
00103   // (statusSet == true) means that setStatus() AND setSong() AND setDataSize() were called!
00104   bool statusSet=false;
00105   bool relayPortSet=false;
00106   bool streamNameSet=false;
00107   bool serverNameSet=false;
00108   bool bitrateSet=false;
00109   bool metaIntervalSet=false;
00110   bool errorSet=false;
00111   // help variables
00112   QString help_string;
00113   bool okay;
00114   qint64 helper_qint64;
00115 
00116   // code
00117   // recognize data
00118   while (!stringList.isEmpty()) {
00119     my_line = stringList.takeLast();  // gives back the last list entry and deletes it
00120 
00121     /* We can't do "my_line=my_line.toLower();" here (what would be very
00122     practical) because we need later the original string with upper AND lower cases when
00123     we want to recognize a song title! */
00124     if (my_line.toLower().startsWith(QString("[ripping...    ] "))) {
00125       if (!statusSet) {
00126         setStatus(is_ripping);
00127         // remove "[ripping...    ] " from the begin of the string:
00128         my_line = my_line.remove(0, 17);
00129         helper_interpretate_metainfo_and_datasize(my_line);
00130         statusSet = true;
00131       };
00132     }
00133     else if (my_line.toLower().startsWith(QString("[buffering - | ] "))) {
00134       if (!statusSet) {
00135         setStatus(is_buffering);
00136         // remove "[buffering - | ] " from the begin of the string:
00137         my_line = my_line.remove(0, 17);
00138         setSong(my_line);
00139         setDataSize(0);
00140         statusSet = true;
00141       };
00142     }
00143     else if (my_line.toLower().startsWith(QString("[skipping...   ] "))) {
00144       if (!statusSet) {
00145         setStatus(is_skipping);
00146         // remove "[skipping...   ] " from the begin of the string:
00147         my_line = my_line.remove(0, 17);
00148         helper_interpretate_metainfo_and_datasize(my_line);
00149         statusSet = true;
00150       };
00151     }
00152     else if (my_line.toLower().startsWith(QString("connecting")) ||
00153               my_line.toLower().startsWith(QString("[getting track name"))) {
00154       if (!statusSet) {
00155         setStatus(is_connecting);
00156         setSong(default_value_of_song());
00157         setDataSize(default_value_of_dataSize());
00158         statusSet = true;
00159       };
00160     }
00161     else if (my_line.toLower().startsWith(QString("shutting down")) ||
00162               my_line.toLower().startsWith(QString("bye.."))) {
00163       if (!statusSet) {
00164         setStatus(is_saving);
00165         setSong(default_value_of_song());
00166         setDataSize(default_value_of_dataSize());
00167         statusSet = true;
00168       };
00169     }
00170     else if (my_line.toLower().startsWith(QString("relay port: "))) {
00171       if (!relayPortSet) {
00172         helper_qint64 = my_line.right(my_line.size()-12).toLongLong(&okay);
00173         if (NOT okay) {
00174           setRelayPort(-1);
00175         } else {
00176           setRelayPort(helper_qint64);
00177         };
00178         relayPortSet = true;
00179       };
00180     }
00181     else if (my_line.toLower().startsWith(QString("stream: "))) {
00182       if (!streamNameSet) {
00183         setStreamName(my_line.right(my_line.size()-8));
00184         streamNameSet = true;
00185       };
00186     }
00187     else if (my_line.toLower().startsWith(QString("server name: "))) {
00188       if (!serverNameSet) {
00189         setServerName(my_line.right(my_line.size()-13));
00190         serverNameSet = true;
00191       };
00192     }
00193     else if (my_line.toLower().startsWith(QString("bitrate: "))) {
00194       if (!bitrateSet) {
00195         helper_qint64 = my_line.right(my_line.size()-9).toLongLong(&okay);
00196         if (NOT okay) {
00197           setBitrate(-1);
00198         } else {
00199           setBitrate(helper_qint64);
00200         };
00201         bitrateSet = true;
00202       };
00203     }
00204     else if (my_line.toLower().startsWith(QString("declared bitrate: "))) {
00205       if (!bitrateSet) {
00206         helper_qint64 = my_line.right(my_line.size()-18).toLongLong(&okay);
00207         if (NOT okay) {
00208           setBitrate(-1);
00209         } else {
00210           setBitrate(helper_qint64);
00211         };
00212         bitrateSet = true;
00213       };
00214     }
00215     else if (my_line.toLower().startsWith(QString("meta interval: "))) {
00216       if (!metaIntervalSet) {
00217         helper_qint64 = my_line.right(my_line.size()-15).toLongLong(&okay);
00218         if (NOT okay) {
00219           setMetaInterval(-1);
00220         } else {
00221           setMetaInterval(helper_qint64);
00222         };
00223         metaIntervalSet = true;
00224       }
00225     }
00226     else if (my_line.toLower().startsWith(QString("error -"))) {
00227       if (!errorSet) {
00228         setError(my_line);
00229         errorSet = true;
00230       };
00231     } else {
00232       kDebug()
00233         << "could not recognize the following string:"
00234         << my_line
00235         << "size:"
00236         << my_line.size();
00237     };
00238   };
00239 }
00240 
00241 void ripping::helper_interpretate_metainfo_and_datasize(QString my_line)
00242 {
00243   // help variables
00244   QString help_string;
00245   bool okay;
00246   qint64 dataSize;
00247 
00248   // code
00249   help_string = my_line.section('[', -1);  /* take all characters
00250   behind the last "[" in the string and put it to help_string */
00251   my_line.truncate(my_line.size() - help_string.size() - 2); /* removes
00252   the help_string from the end and also " [" before the help_string -> so
00253   there stays only the song (Okay, it's not really the song. It's the
00254   metadata.) itself. */
00255 
00256   // metainfo
00257   setSong(my_line);
00258 
00259   // data size
00260   // remove ']' and whitespace and convert tu uppercase letters:
00261   help_string=help_string.remove(']').simplified().toUpper();
00262   if (help_string.endsWith(QString("KB")) OR help_string.endsWith('K')) {
00263     if (help_string.endsWith(QString("KB"))) {
00264       help_string.truncate(help_string.size() - 2);
00265     } else {
00266       help_string.truncate(help_string.size() - 1);
00267     };
00268     dataSize = help_string.toLongLong(&okay) * 1024;
00269   } else {
00270     if (help_string.endsWith(QString("MB")) OR help_string.endsWith('M')) {
00271       if (help_string.endsWith(QString("MB"))) {
00272         help_string.truncate(help_string.size() - 2);
00273       } else {
00274         help_string.truncate(help_string.size() - 1);
00275       };
00276       dataSize = help_string.toDouble(&okay) * 1024 * 1024;
00277     } else {
00278       if (help_string.endsWith('B')) {
00279         // == ends with "B", but not "MB" or "KB" (see previous contitions)
00280         help_string.truncate(help_string.size() - 1);
00281         dataSize = help_string.toLongLong(&okay);
00282       } else {
00283         okay = false;
00284       };
00285     };
00286   };
00287   if (NOT okay) {
00288     setDataSize(-1);
00289   } else {
00290     setDataSize(dataSize);
00291   };
00292 }
00293 
00294 PropertyValue ripping::streamName() const
00295 {
00296   return internal_streamName;
00297 }
00298 
00299 PropertyValue ripping::formatedStreamName(const QString & theStreamName)
00300 {
00301     // variables
00302     PropertyValue temp_streamName;
00303 
00304     temp_streamName.internalValue = theStreamName;
00305     if (theStreamName == "Streamripper_rips") {
00306       temp_streamName.formatedValue = theStreamName;
00307       temp_streamName.type = PropertyValue::error;
00308       temp_streamName.toolTip = i18nc(
00309         "@info:tooltip Leave Streamripper_rips unchanged, it is a generic name of a directory",
00310         "Nameless stream. Using <emphasis>Streamripper_rips</emphasis> as replacement.");
00311       temp_streamName.whatsThis = i18nc(
00312         "@info:whatsthis Leave Streamripper_rips unchanged, it is a generic name of a directory",
00313         "This stream does not send a name in his meta data. "
00314           "<emphasis>Streamripper_rips</emphasis> is used as replacement, and "
00315           "you find the recorded files in the directory of the same name.");
00316     } else {
00317       if (theStreamName.isEmpty()) {
00318         temp_streamName.formatedValue = i18nc("@item", "not recognized");
00319         temp_streamName.type = PropertyValue::unset;
00320         temp_streamName.toolTip = i18nc(
00321           "@info:tooltip",
00322           "Could not connect to server.");
00323         temp_streamName.whatsThis = i18nc(
00324           "@info:whatsthis",
00325           "Could not connect to the specified server. So the stream name could not be recognized.");
00326       } else {
00327         temp_streamName.formatedValue = theStreamName;
00328         temp_streamName.type = PropertyValue::value;
00329       };
00330     };
00331 
00332     return temp_streamName;
00333 }
00334 
00335 void ripping::setStreamName(const QString & newStreamName)
00336 {
00337   if (internal_streamName.internalValue.toString() != newStreamName) {
00338     internal_streamName = formatedStreamName(newStreamName);
00339     emit streamNameChanged(index(), internal_streamName);
00340   };
00341 }
00342 
00343 QString ripping::default_value_of_streamName()
00344 {
00345   return QString();
00346 }
00347 
00348 PropertyValue ripping::formatedServerName(const QString & theServerName)
00349 {
00350     // variables
00351     PropertyValue temp_serverName;
00352 
00353     // code
00354     temp_serverName.internalValue = theServerName;
00355     temp_serverName.formatedValue = theServerName;
00356     if (theServerName.isEmpty()) {
00357       temp_serverName.type = PropertyValue::unset;
00358     } else {
00359       temp_serverName.type = PropertyValue::value;
00360     };
00361     // there's never a toolTip or whatsThis, so no need to clear them.
00362 
00363     return temp_serverName;
00364 }
00365 
00366 PropertyValue ripping::serverName() const
00367 {
00368   return internal_serverName;
00369 }
00370 
00371 void ripping::setServerName(const QString & newServerName)
00372 {
00373   if (internal_serverName.internalValue.toString() != newServerName) {
00374     internal_serverName = formatedServerName(newServerName);
00375     emit serverNameChanged(index(), internal_serverName);
00376   };
00377 }
00378 
00379 QString ripping::default_value_of_serverName()
00380 {
00381   return QString();
00382 }
00383 
00384 PropertyValue ripping::status() const
00385 {
00386   return internal_status;
00387 }
00388 
00389 PropertyValue ripping::formatedStatus(const statusType theStatus)
00390 {
00391     // variables
00392     PropertyValue temp_status;
00393 
00394     // code
00395     temp_status.internalValue.setValue(theStatus);
00396     if (theStatus == idle) {
00397       temp_status.type = PropertyValue::unset;
00398     } else {
00399       temp_status.type = PropertyValue::value;
00400     };
00401     switch (theStatus) {
00402       case idle:
00403         temp_status.formatedValue.clear();
00404         temp_status.toolTip.clear();
00405         temp_status.whatsThis.clear();
00406         break;
00407       case is_starting:
00408         temp_status.formatedValue = i18nc("@item status of streamripper", "Starting...");
00409         temp_status.toolTip = i18nc("@info:tooltip", "Invocing Streamripper...");
00410         temp_status.whatsThis = i18nc(
00411           "@info:whatsthis",
00412           "KRadioRipper is invocing Streamripper, the program used to "
00413             "perform the recording process.");
00414         break;
00415       case is_connecting:
00416         temp_status.formatedValue = i18nc("@item status of Streamripper", "Connecting...");
00417         temp_status.toolTip = i18nc("@info:tooltip", "Connecting with the stream server...");
00418         temp_status.whatsThis = i18nc("@info:whatsthis",
00419                                       "Streamripper is connecting with the stream server.");
00420         break;
00421       case is_buffering:
00422         temp_status.formatedValue = i18nc("@item status of Streamripper", "Buffering...");
00423         temp_status.toolTip = i18nc("@info:tooltip", "Buffering stream data...");
00424         temp_status.whatsThis = i18nc(
00425           "@info:whatsthis",
00426           "Stream data is buffered before starting the recording process.");
00427         break;
00428       case is_skipping:
00429         temp_status.formatedValue = i18nc("@item status of Streamripper", "Skipping...");
00430         temp_status.toolTip = i18nc("@info:tooltip", "Skipping the actual track...");
00431         temp_status.whatsThis = i18nc(
00432           "@info:whatsthis",
00433           "<para>KRadioRipper skips by default the first track, because this track will lack "
00434             "the begin.</para><para>You can change this behavior by editing the settings of the "
00435             "stream.</para>");
00436         break;
00437       case is_ripping:
00438         temp_status.formatedValue = i18nc("@item status of Streamripper", "Recording...");
00439         temp_status.toolTip = i18nc("@info:tooltip", "Recording the stream...");
00440         temp_status.whatsThis = i18nc("@info:whatsthis", "KRadioRipper is recording the stream.");
00441         break;
00442       case is_saving:
00443         temp_status.formatedValue = i18nc("@item status of Streamripper", "Saving...");
00444         temp_status.toolTip = i18nc("@info:tooltip", "Saving files...");
00445         temp_status.whatsThis = i18nc(
00446           "@info:whatsthis",
00447           "The buffer is processed and the last files are saved.");
00448         break;
00449     };
00450 
00451     return temp_status;
00452 }
00453 
00454 void ripping::setStatus(const statusType newStatus)
00455 {
00456   if (internal_status.internalValue.value<statusType>() != newStatus) {
00457     internal_status = formatedStatus(newStatus);
00458     emit statusChanged(index(), internal_status);
00459     refreshRelayPort();
00460   };
00461 
00462   bool newIsRunning = (newStatus != idle);
00463   if (internal_isRunning != newIsRunning) {
00464     internal_isRunning = newIsRunning;
00465     if (newIsRunning) {
00466       emit running();
00467     } else {
00468       emit not_running();
00469     };
00470   };
00471 }
00472 
00473 ripping::statusType ripping::default_value_of_status()
00474 {
00475   return idle;
00476 }
00477 
00478 PropertyValue ripping::error() const
00479 {
00480   return internal_error;
00481 }
00482 
00483 PropertyValue ripping::formatedError(const QString & theError)
00484 {
00485   // variables
00486   PropertyValue temp_error;
00487   QString streamripper_error_number;
00488   QString temp_message_of_unknown_id;
00489 
00490   // code
00491   temp_error.internalValue = theError;
00492 
00493   if (theError.isEmpty()) {
00494     temp_error.type = PropertyValue::unset;
00495   } else {
00496     temp_error.type = PropertyValue::value;
00497   };
00498 
00499   // message of format "error -123 bug description"
00500   if (theError.startsWith(QString("error -"))) {
00501     streamripper_error_number = theError.right(theError.size() - 7);  // without "error -"
00502     streamripper_error_number = streamripper_error_number.section(' ', 0, 0);
00503     switch (streamripper_error_number.toLongLong()) {
00504       case 3: // SR_ERROR_INVALID_URL
00505       case 6: // SR_ERROR_CANT_RESOLVE_HOSTNAME
00506         temp_error.formatedValue = i18nc("@item error message", "connection failed");
00507         temp_error.toolTip = i18nc("@info:tooltip error message", "Could not connect to server");
00508         temp_error.whatsThis = i18nc(
00509           "@info:whatsthis error message",
00510           "Either the URL is invalid or the corresponding server does not exist.");
00511         break;
00512       case 7: // SR_ERROR_RECV_FAILED
00513         temp_error.formatedValue = i18nc("@item error message", "incompatible stream");
00514         temp_error.toolTip = i18nc("@info:tooltip error message",
00515                                    "KRadioRipper can not record this type of stream.");
00516         temp_error.whatsThis = i18nc("@info:whatsthis error message",
00517                                       "Streamripper (and so also KRadioRipper) can only "
00518                                       "record shoutcast and icecast streams.");
00519         break;
00520       case 56: // HTTP:403 - Access Forbidden (try changing the UserAgent)
00521         temp_error.formatedValue = i18nc("@item error message", "connection refused");
00522         temp_error.toolTip = i18nc("@info:tooltip error message",
00523                                    "Try changing the user agent string");
00524         temp_error.whatsThis = i18nc(
00525           "@info:whatsthis error message",
00526           "<para>The server has refused the connection.</para><para>You can try to use another "
00527           "user agent string - maybe the server will accept this.</para>");
00528         break;
00529       case 64: // SR_ERROR_CANT_PARSE_PLS
00530         temp_error.formatedValue = i18nc("@item error message", "no stream");
00531         temp_error.toolTip = i18nc("@info:tooltip error message", "invalid playlist");
00532         temp_error.whatsThis = i18nc("@info:whatsthis error message",
00533                                      "The URL does not point directly to a stream, but to a "
00534                                        "playlist. And the playlist is invalid.");
00535         break;
00536       case 36: // SR_ERROR_CANT_CREATE_FILE
00537       case 1001: // BAD_DOWNLOAD_DIRECTORY
00538         temp_error.formatedValue = i18nc("@item error message", "bad download directory");
00539         temp_error.toolTip = i18nc(
00540           "@info:tooltip error message",
00541           "KRadioRipper could not write the file because the download directory is not writable.");
00542         temp_error.whatsThis = i18nc(
00543           "@info:whatsthis error message",
00544           "<para>The download directory is not accessible. Either "
00545             "it does not exist or you do not have sufficient access rights.</para><para>You "
00546             "can change the download directory at <emphasis>Settings</emphasis>, <emphasis>"
00547             "Configure KRadioRipper...</emphasis>, <emphasis>Saving</emphasis>"
00548             "</para>");
00549         break;
00550       default:
00551         temp_message_of_unknown_id = theError.right(theError.size() - 7);  // without "error -"
00552         temp_message_of_unknown_id = temp_message_of_unknown_id.section(' ', 1, 1);
00553         temp_error.formatedValue = i18nc("@item error message for an error that was not recognized "
00554                                            "(item 1: ID number of the error; item 2: error "
00555                                            "description in english",
00556                                          "Error %1: %2",
00557                                          streamripper_error_number.toLongLong(),
00558                                          temp_message_of_unknown_id);
00559         break;
00560     };
00561   } else {
00562     temp_error.formatedValue = theError;
00563   };
00564 
00565   return temp_error;
00566 }
00567 
00568 void ripping::setError(const QString & newError)
00569 {
00570   if ((internal_error.internalValue.toString() != newError)) {
00571     /* we can't use != directly because QVariant doesn't support
00572     this for custom data types (but also doesn't generate a
00573     compiler error). */
00574     internal_error = formatedError(newError);
00575     emit errorChanged(index(), internal_error);
00576     refreshRelayPort();
00577   };
00578 }
00579 
00580 QString ripping::default_value_of_error()
00581 {
00582   return QString();
00583 }
00584 
00585 PropertyValue ripping::song() const
00586 {
00587   return internal_song;
00588 }
00589 
00590 PropertyValue ripping::formatedSong(const QString & theSong)
00591 {
00592     // variables
00593     PropertyValue temp_song;
00594 
00595     // code
00596     temp_song.internalValue = theSong;
00597     if ((theSong.isEmpty()) OR (theSong EQUAL " - ")) {
00598       temp_song.type = PropertyValue::unset;
00599       temp_song.formatedValue.clear();
00600     } else {
00601       temp_song.type = PropertyValue::value;
00602       temp_song.formatedValue = theSong;
00603     };
00604     // there's never a toolTip or whatsThis, so no need to clear them.
00605     return temp_song;
00606 }
00607 
00608 void ripping::setSong(const QString & newSong)
00609 {
00610   if (internal_song.internalValue.toString() != newSong) {
00611     internal_song = formatedSong(newSong);
00612     emit songChanged(index(), internal_song);
00613   };
00614 }
00615 
00616 QString ripping::default_value_of_song()
00617 {
00618   return QString();
00619 }
00620 
00621 PropertyValue ripping::dataSize() const
00622 {
00623   return internal_dataSize;
00624 }
00625 
00626 PropertyValue ripping::formatedDataSize(const qint64 theDataSize)
00627 {
00628   // variables
00629   PropertyValue temp_dataSize;
00630 
00631   // code
00632   temp_dataSize.internalValue = theDataSize;
00633   if (theDataSize == (-1)) {
00634     temp_dataSize.type = PropertyValue::error;
00635     temp_dataSize.formatedValue = i18nc("@item", "error");
00636     temp_dataSize.toolTip = i18nc ("@info:tooltip", "Error determinating track size");
00637     temp_dataSize.whatsThis = i18nc (
00638       "@info:whatsthis",
00639       "The track size could not be determinated. Please report this as a bug.");
00640   } else {
00641     if (theDataSize >= 0) {
00642       temp_dataSize.type = PropertyValue::value;
00643       temp_dataSize.formatedValue = ki18nc("@item The unit is MiB instead of MB. See "
00644                                              "http://en.wikipedia.org/wiki/Binary_prefix "
00645                                              "for details.",
00646                                            "%1 MiB")
00647           .subs(double(theDataSize) / (1024 * 1024), 0, 'f', 2).toString();
00648       temp_dataSize.whatsThis = i18nc("@info:whatsthis",
00649                                        "<para>The size of the track in MiB.</para><para>MiB "
00650                                        "has a binary prefix which means 1024 * 1024 B = "
00651                                        "1048576 B (different from MB which would mean "
00652                                        "1000000 B).</para>");
00653     } else { // theDataSize < -1
00654       temp_dataSize.type = PropertyValue::unset;
00655       temp_dataSize.formatedValue.clear();
00656       temp_dataSize.whatsThis.clear();
00657     };
00658     temp_dataSize.toolTip.clear();
00659   };
00660   temp_dataSize.formatedValueAlignment = (Qt::AlignRight | Qt::AlignVCenter);
00661 
00662   return temp_dataSize;
00663 }
00664 
00665 void ripping::setDataSize(const qint64 newDataSize)
00666 {
00667   if (internal_dataSize.internalValue.toLongLong() != newDataSize) {
00668     internal_dataSize = formatedDataSize(newDataSize);
00669     emit dataSizeChanged(index(), internal_dataSize);
00670   };
00671 }
00672 
00673 qint64 ripping::default_value_of_dataSize()
00674 {
00675   return (-2);
00676 }
00677 
00678 bool ripping::default_value_of_isRunning()
00679 {
00680   return false;
00681 }
00682 
00683 bool ripping::isRunning() const
00684 {
00685   return internal_isRunning;
00686 }
00687 
00688 PropertyValue ripping::relayPort() const
00689 {
00690   return internal_relayPort;
00691 }
00692 
00693 PropertyValue ripping::formatedRelayPort(const qint64 theRelayPort)
00694 {
00695     // variables
00696     PropertyValue temp_relayPort;
00697 
00698     // code
00699     temp_relayPort.internalValue = theRelayPort;
00700     if (theRelayPort == (-1)) {
00701       temp_relayPort.type = PropertyValue::error;
00702       temp_relayPort.formatedValue = i18nc(
00703         "@item",
00704         "error");
00705       temp_relayPort.toolTip = i18nc (
00706         "@info:tooltip",
00707         "Error determinating relay server port");
00708       temp_relayPort.whatsThis = i18nc (
00709         "@info:whatsthis",
00710         "The port of the relay server could not be determinated. Please report this as a bug.");
00711     } else {
00712       if (theRelayPort >= 0) {
00713         temp_relayPort.type = PropertyValue::value;
00714         temp_relayPort.formatedValue = KGlobal::locale()->formatLong(theRelayPort);
00715       } else { // relayPort < -1
00716         temp_relayPort.type = PropertyValue::unset;
00717         temp_relayPort.formatedValue.clear();
00718       };
00719       temp_relayPort.toolTip.clear();
00720       temp_relayPort.whatsThis.clear();
00721     };
00722     temp_relayPort.formatedValueAlignment = (Qt::AlignRight | Qt::AlignVCenter);
00723 
00724     return temp_relayPort;
00725 }
00726 
00727 void ripping::refreshRelayPort()
00728 {
00729   statusType actualStatus = status().internalValue.value<statusType>();
00730   if ((actualStatus == is_skipping || actualStatus == is_ripping) &&
00731       (error().type == PropertyValue::unset)) {
00732     internal_relayPort = formatedRelayPort(lastRecognizedRelayPort);
00733   } else {
00734     internal_relayPort = formatedRelayPort(-2);
00735   };
00736   emit relayPortChanged(index(), internal_relayPort);
00737 }
00738 
00739 void ripping::setRelayPort(const qint64 newRelayPort)
00740 {
00741   if (lastRecognizedRelayPort != newRelayPort) {
00742     lastRecognizedRelayPort = newRelayPort;
00743     refreshRelayPort();
00744   };
00745 }
00746 
00747 qint64 ripping::default_value_of_relayPort()
00748 {
00749   return (-2);
00750 }
00751 
00752 PropertyValue ripping::bitrate() const
00753 {
00754   return internal_bitrate;
00755 }
00756 
00757 PropertyValue ripping::formatedBitrate(const qint64 theBitrate)
00758 {
00759     // variables
00760     PropertyValue temp_bitrate;
00761 
00762     // code
00763     temp_bitrate.internalValue=theBitrate;
00764 
00765     if (theBitrate >= 1) {  // a valid bitrate...
00766       temp_bitrate.formatedValue = i18ncp(
00767         "@item This makes a nicly formated string for the bitrate of a stream - %1 is an integer. "
00768           "WARNING: Unit has changed! It is now kbit instead of Kibit. "
00769           "This means 1000 bit (NOT 1024).",
00770         "%1 kbit/s",
00771         "%1 kbit/s",
00772         theBitrate);
00773       temp_bitrate.type = PropertyValue::value;
00774       temp_bitrate.toolTip = i18nc("@info:tooltip", "declared bit rate");
00775       temp_bitrate.whatsThis = i18nc(
00776         "@info:whatsthis WARNING Unit has changed from binary prefix to SI prefix",
00777         "<para>The declared bit rate of the stream in kbit/s.</para><para>kbit has an SI prefix "
00778           "which means 1000 bit (different from Kibit which would mean 1024 bit). So 1 kbit/s "
00779           "means 1000 bits per second.</para>");
00780     } else {
00781       if (theBitrate >= -1) {  // "0" or "-1" (error during recognization)
00782         temp_bitrate.formatedValue = i18nc(
00783           "@item This makes a nicly formated string for the bitrate of a stream.",
00784           "Unable to recognize bitrate.");
00785         temp_bitrate.type = PropertyValue::error;
00786         temp_bitrate.toolTip = i18nc (
00787           "@info:tooltip",
00788           "Error determinating bit rate");
00789         temp_bitrate.whatsThis = i18nc (
00790           "@info:whatsthis",
00791           "The bit rate could not be determinated. Please report this as a bug.");
00792       } else { // "-2" or smaller (no value set)
00793         temp_bitrate.formatedValue.clear();
00794         temp_bitrate.type = PropertyValue::unset;
00795         temp_bitrate.toolTip.clear();
00796         temp_bitrate.whatsThis.clear();
00797       }
00798     };
00799 
00800     temp_bitrate.formatedValueAlignment = (Qt::AlignRight | Qt::AlignVCenter);
00801 
00802     return temp_bitrate;
00803 }
00804 
00805 void ripping::setBitrate(const qint64 newBitrate)
00806 {
00807   if (internal_bitrate.internalValue.toLongLong() != newBitrate) {
00808     internal_bitrate = formatedBitrate(newBitrate);
00809     emit bitrateChanged(index(), internal_bitrate);
00810   };
00811 }
00812 
00813 qint64 ripping::default_value_of_bitrate()
00814 {
00815   return (-2);
00816 }
00817 
00818 PropertyValue ripping::metaInterval() const
00819 {
00820   return internal_metaInterval;
00821 }
00822 
00823 PropertyValue ripping::formatedMetaInterval(const qint64 theMetaInterval)
00824 {
00825     // variables
00826     PropertyValue temp_metaInterval;
00827 
00828     // code
00829     temp_metaInterval.internalValue = theMetaInterval;
00830     if (theMetaInterval > 0) {     // metaInterval is 1 or bigger
00831       temp_metaInterval.formatedValue = //KGlobal::locale()->formatLong(theMetaInterval);
00832         ki18nc("@item The unit is KiB instead of kB. See "
00833                  "http://en.wikipedia.org/wiki/Binary_prefix for details.",
00834                "%1 KiB")
00835           .subs(qint64(theMetaInterval / 1024)).toString();
00836       temp_metaInterval.type = PropertyValue::value;
00837     } else {
00838       if (theMetaInterval >= -1) { // metaInterval is 0 or -1
00839         temp_metaInterval.formatedValue = i18nc(
00840           "@item",
00841           "error");
00842         temp_metaInterval.type = PropertyValue::error;
00843       } else {
00844         temp_metaInterval.formatedValue.clear();
00845         temp_metaInterval.type = PropertyValue::unset;
00846       };
00847     };
00848     temp_metaInterval.formatedValueAlignment = (Qt::AlignRight | Qt::AlignVCenter);
00849 
00850     return temp_metaInterval;
00851 }
00852 
00853 void ripping::setMetaInterval(const qint64 newMetaInterval)
00854 {
00855   if (internal_metaInterval.internalValue != newMetaInterval) {
00856     internal_metaInterval = formatedMetaInterval(newMetaInterval);
00857     emit metaIntervalChanged(index(), internal_metaInterval);
00858   };
00859 }
00860 
00861 qint64 ripping::default_value_of_metaInterval()
00862 {
00863   return (-2);
00864 }
00865 
00866 PropertyValue ripping::metaInterval_milliSeconds() const
00867 {
00868   // variables
00869   qint64 temp;
00870 
00871   // code
00872   if (bitrate().type == PropertyValue::value &&  // both are values...
00873       metaInterval().type == PropertyValue::value &&
00874       bitrate().internalValue.toLongLong() > 0) { // Prevent from dividing by 0. Is redundant to
00875                                             // bitrate.type == PropertyValue::value.
00876                                             // Just to be sure, also with further changes...
00877     /* bitrate has the unit kbit/s...
00878     * kbit/s = (1000 bit)/s
00879     *        = (1000 bit)/(1000 ms)
00880     *        = bit/ms.
00881     * Conclusion: kbit/s=bit/ms, so we can think of bitrate as bit/ms.
00882     *
00883     * metaInterval has the unit Byte. So we take the value *8, so we are
00884     * in the unit bit.
00885     *
00886     * So we calculate: (metaInterval*8)/bitrate. This works fine for the units:
00887     * bit/(bit/ms)=ms, so we get the metaInterval in ms! */
00888     temp = (metaInterval().internalValue.toLongLong() * 8) / bitrate().internalValue.toLongLong();
00889   } else if (bitrate().type == PropertyValue::unset ||  // at least one is unset...
00890              metaInterval().type == PropertyValue::unset) {
00891     temp = -2;
00892   } else {
00893     temp = -1;
00894   };
00895   return formatedMetaInterval_milliSeconds(temp);
00896 }
00897 
00898 PropertyValue ripping::formatedMetaInterval_milliSeconds(const qint64 theMetaInterval)
00899 {
00900     // variables
00901     PropertyValue temp_metaInterval_milliSeconds;
00902 
00903     // code
00904     temp_metaInterval_milliSeconds.internalValue = theMetaInterval;
00905     if (theMetaInterval > 0) {     // metaInterval is 1 or bigger
00906       temp_metaInterval_milliSeconds.formatedValue =
00907         ki18nc("@item milliseconds", "%1 ms").subs(theMetaInterval).toString();
00908       temp_metaInterval_milliSeconds.type = PropertyValue::value;
00909     } else {
00910       if (theMetaInterval >= -1) { // metaInterval is 0 or -1
00911         temp_metaInterval_milliSeconds.formatedValue = i18nc(
00912           "@item",
00913           "error");
00914         temp_metaInterval_milliSeconds.type = PropertyValue::error;
00915       } else {
00916         temp_metaInterval_milliSeconds.formatedValue.clear();
00917         temp_metaInterval_milliSeconds.type = PropertyValue::unset;
00918       };
00919     };
00920     temp_metaInterval_milliSeconds.formatedValueAlignment = (Qt::AlignRight | Qt::AlignVCenter);
00921 
00922     return temp_metaInterval_milliSeconds;
00923 }
00924 
00925 void ripping::errorOccured(const QProcess::ProcessError error)
00926 {
00927   // variables
00928   QFileInfo m_file_info(settings_general::streamripperCommand());
00929 
00930   // code
00931   switch (error) {
00932     case QProcess::FailedToStart:
00933       if (m_file_info.exists()) {
00934         setError(i18nc(
00935           "@item streamripper error",
00936           "Insufficient permissions to invoke Streamripper."));
00937       } else {
00938         setError(i18nc("@item streamripper error", "Streamripper binary not found."));
00939       };
00940       break;
00941     case QProcess::Crashed:
00942       setError(i18nc("@item streamripper error", "Streamripper crashed."));
00943       break;
00944     case QProcess::Timedout:
00945       setError(i18nc("@item streamripper error", "Streamripper does not react."));
00946       break;
00947     default:  //ReadError, WriteError, UnknownError
00948       setError(i18nc("@item streamripper error", "Error accessing Streamripper."));
00949       break;
00950   };
00951 }
00952 
00953 void ripping::streamripperStateChange(const QProcess::ProcessState newState)
00954 {
00955   if (newState == QProcess::NotRunning) {
00956     setStatus(default_value_of_status());
00957     setSong(default_value_of_song());
00958     /* Here, we can't determinate
00959     *  - if the program has terminated normally (good) or
00960     *  - if it has crashed or
00961     *  - if it couldn't even be started, because the binary wasn't found or
00962     *    wasn't executable.
00963     *
00964     *  In the last 2 cases, we should display an error message, depending on
00965     *  _which_ error occurred. But we can't use m_process.error() to determinate
00966     *  the error type, because QProcess emits the signal stateChanged() (to which
00967     *  this slot is connected) _before_ it actualizes the property error().
00968     *  Because of this, the error message is set by the slot errorOccured(), who
00969     *  is connected to the signal error(). */
00970     setDataSize(default_value_of_dataSize());
00971     setRelayPort(default_value_of_relayPort());
00972   };
00973 }
00974 
00975 QStringList ripping::parameterList() const
00976 {
00977   // variables
00978   QStringList parameters;
00979   QString temp;
00980 
00981   //code
00982   parameters.append(serverUri());
00983 
00984   parameters.append(QString("-t")); // don't override files in the incomplete dir
00985                                     // but make instead a save copy of the old file
00986                                     // by appending an index. I'm not sure if this
00987                                     // is absolutly needed, but at least it's not bad.
00988 
00989   temp = proxyinfo::proxyserver(serverUri()).at(0);
00990   if (temp != "direct://") {
00991     parameters.append(QString("-p"));
00992     parameters.append(temp);
00993   };
00994 
00995   if (settings_general::createRelayServer()) { // create relay server
00996     // create a relay server at (or up to) port X!
00997     parameters.append(QString("-r"));
00998     parameters.append(QString::number(settings_general::preferedPortForRelayServer()));
00999 
01000     // the number of connections is limited to which number?
01001     parameters.append(QString("-R"));
01002     if (settings_general::limitConnections()) {
01003       parameters.append(QString::number(settings_general::limitConnectionsToX()));
01004     } else {
01005       parameters.append(QString("0"));
01006     };
01007   };
01008 
01009   parameters.append(QString("-c"));  // don't auto-reconnect  TODO: implement this on my own way!
01010 
01011   return parameters;
01012 }
01013 
01014 QString ripping::streamripperCommand() const
01015 {
01016   return settings_general::streamripperCommand();
01017 }
01018 
01019 void ripping::startStreamripper()
01020 {
01021   // variables
01022   QFileInfo * dir;
01023   QDir *dirCreator;
01024   QString temp;
01025 
01026   // code
01027   if (m_process.state() == QProcess::NotRunning) { // TODO else if the process is
01028     // yet shutting down or has yet an error -> einreihen für späteren automatischen Neustart!
01029     setError(QString());
01030     setStatus(is_starting);
01031     temp = workingDirectory();
01032     if (temp.startsWith(QLatin1String("file://"))) {
01033       temp.remove(0, 7);
01034     };
01035     dir = new QFileInfo(temp);
01036     // If dir doesn't exist and is an absulute path: Create it (if possible).
01037     if ((!dir->exists()) && dir->isAbsolute()) {
01038       dirCreator = new QDir(/*dir*/);
01039       dirCreator->mkpath(dir->filePath());
01040       dir->refresh();
01041     };
01042     if (dir->exists() && dir->isAbsolute() && dir->isDir() &&
01043         dir->isReadable() && dir->isWritable()) {
01044       m_process.setWorkingDirectory(workingDirectory());
01045       streamripper_base::startStreamripper();
01046     } else {
01047       // Emulate streamripper error message to get automatically a formated message:
01048       setError("error -1001 [BAD_DOWNLOAD_DIRECTORY]");
01049       setStatus(idle);
01050     };
01051   };
01052 }
01053 
01054 void ripping::shutDown()
01055 {
01056   m_process.terminate();
01057   /* TODO eventuelle Einreihung für Neustart wieder löschen! (wenn jemand startet,
01058   während gerade runtergefahren wird) */
01059 }
01060 
01061 bool ripping::doesTheUserWantsThatTheStreamIsRipping(ripping::statusType theStatus)
01062 {
01063   return (!((theStatus == ripping::idle) OR (theStatus == ripping::is_saving)));
01064 }
01065 
01066 bool ripping::doesTheUserWantsThatTheStreamIsRipping()
01067 {
01068   return doesTheUserWantsThatTheStreamIsRipping(
01069            internal_status.internalValue.value<statusType>());
01070 }

doxygen