ripping.cpp

Go to the documentation of this file.
00001 /*
00002     Copyright (C) 2008-2010  Lukas Sommer < SommerLuk at gmail dot com >
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(void *, 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           // Sometimes, the bitrate is given in bit/s instead of kbit/s.
00200           // Furthermore, it's unclear if SI prefixes or binary prefixes
00201           // are used. The calculation we do here is not necesarrily correct,
00202           // but seems to work quite well in practice.
00203           if (helper_qint64 > 1024) {
00204             helper_qint64 = helper_qint64 / 1024;
00205           };
00206           setBitrate(helper_qint64);
00207         };
00208         bitrateSet = true;
00209       };
00210     }
00211     else if (my_line.toLower().startsWith(QString("declared bitrate: "))) {
00212       if (!bitrateSet) {
00213         helper_qint64 = my_line.right(my_line.size()-18).toLongLong(&okay);
00214         if (NOT okay) {
00215           setBitrate(-1);
00216         } else {
00217           // Sometimes, the bitrate is given in bit/s instead of kbit/s.
00218           // Furthermore, it's unclear if SI prefixes or binary prefixes
00219           // are used. The calculation we do here is not necesarrily correct,
00220           // but seems to work quite well in practice.
00221           if (helper_qint64 > 1024) {
00222             helper_qint64 = helper_qint64 / 1024;
00223           };
00224           setBitrate(helper_qint64);
00225         };
00226         bitrateSet = true;
00227       };
00228     }
00229     else if (my_line.toLower().startsWith(QString("meta interval: "))) {
00230       if (!metaIntervalSet) {
00231         helper_qint64 = my_line.right(my_line.size()-15).toLongLong(&okay);
00232         if (NOT okay) {
00233           setMetaInterval(-1);
00234         } else {
00235           setMetaInterval(helper_qint64);
00236         };
00237         metaIntervalSet = true;
00238       }
00239     }
00240     else if (my_line.toLower().startsWith(QString("error -"))) {
00241       if (!errorSet) {
00242         setError(my_line);
00243         errorSet = true;
00244       };
00245     } else {
00246       kDebug()
00247         << "could not recognize the following string:"
00248         << my_line
00249         << "size:"
00250         << my_line.size();
00251     };
00252   };
00253 }
00254 
00255 void ripping::helper_interpretate_metainfo_and_datasize(QString my_line)
00256 {
00257   // help variables
00258   QString help_string;
00259   bool okay;
00260   qint64 dataSize;
00261 
00262   // code
00263   help_string = my_line.section('[', -1);  /* take all characters
00264   behind the last "[" in the string and put it to help_string */
00265   my_line.truncate(my_line.size() - help_string.size() - 2); /* removes
00266   the help_string from the end and also " [" before the help_string -> so
00267   there stays only the song (Okay, it's not really the song. It's the
00268   metadata.) itself. */
00269 
00270   // metainfo
00271   setSong(my_line);
00272 
00273   // data size
00274   // remove ']' and whitespace and convert tu uppercase letters:
00275   help_string=help_string.remove(']').simplified().toUpper();
00276   if (help_string.endsWith(QString("KB")) OR help_string.endsWith('K')) {
00277     if (help_string.endsWith(QString("KB"))) {
00278       help_string.truncate(help_string.size() - 2);
00279     } else {
00280       help_string.truncate(help_string.size() - 1);
00281     };
00282     dataSize = help_string.toLongLong(&okay) * 1024;
00283   } else {
00284     if (help_string.endsWith(QString("MB")) OR help_string.endsWith('M')) {
00285       if (help_string.endsWith(QString("MB"))) {
00286         help_string.truncate(help_string.size() - 2);
00287       } else {
00288         help_string.truncate(help_string.size() - 1);
00289       };
00290       dataSize = help_string.toDouble(&okay) * 1024 * 1024;
00291     } else {
00292       if (help_string.endsWith('B')) {
00293         // == ends with "B", but not "MB" or "KB" (see previous contitions)
00294         help_string.truncate(help_string.size() - 1);
00295         dataSize = help_string.toLongLong(&okay);
00296       } else {
00297         okay = false;
00298       };
00299     };
00300   };
00301   if (NOT okay) {
00302     setDataSize(-1);
00303   } else {
00304     setDataSize(dataSize);
00305   };
00306 }
00307 
00308 PropertyValue ripping::streamName() const
00309 {
00310   return internal_streamName;
00311 }
00312 
00313 PropertyValue ripping::formatedStreamName(const QString & theStreamName)
00314 {
00315     // variables
00316     PropertyValue temp_streamName;
00317 
00318     temp_streamName.internalValue = theStreamName;
00319     if (theStreamName == "Streamripper_rips") {
00320       temp_streamName.formatedValue = theStreamName;
00321       temp_streamName.type = PropertyValue::error;
00322       temp_streamName.toolTip = i18nc(
00323         "@info:tooltip Leave Streamripper_rips unchanged, it is a generic name of a directory",
00324         "Nameless stream. Using <emphasis>Streamripper_rips</emphasis> as replacement.");
00325       temp_streamName.whatsThis = i18nc(
00326         "@info:whatsthis Leave Streamripper_rips unchanged, it is a generic name of a directory",
00327         "This stream does not send a name in its meta data. "
00328           "<emphasis>Streamripper_rips</emphasis> is used as replacement, and you find the "
00329           "recorded files in the directory of the same name.");
00330     } else {
00331       if (theStreamName.isEmpty()) {
00332         temp_streamName.formatedValue = i18nc("@item", "not recognized");
00333         temp_streamName.type = PropertyValue::unset;
00334         temp_streamName.toolTip = i18nc(
00335           "@info:tooltip",
00336           "Could not connect to server.");
00337         temp_streamName.whatsThis = i18nc(
00338           "@info:whatsthis",
00339           "Could not connect to the specified server. So the stream name could not be recognized.");
00340       } else {
00341         temp_streamName.formatedValue = theStreamName;
00342         temp_streamName.type = PropertyValue::value;
00343       };
00344     };
00345 
00346     return temp_streamName;
00347 }
00348 
00349 void ripping::setStreamName(const QString & newStreamName)
00350 {
00351   if (internal_streamName.internalValue.toString() != newStreamName) {
00352     internal_streamName = formatedStreamName(newStreamName);
00353     emit streamNameChanged(index(), internal_streamName);
00354   };
00355 }
00356 
00357 QString ripping::default_value_of_streamName()
00358 {
00359   return QString();
00360 }
00361 
00362 PropertyValue ripping::formatedServerName(const QString & theServerName)
00363 {
00364     // variables
00365     PropertyValue temp_serverName;
00366 
00367     // code
00368     temp_serverName.internalValue = theServerName;
00369     temp_serverName.formatedValue = theServerName;
00370     if (theServerName.isEmpty()) {
00371       temp_serverName.type = PropertyValue::unset;
00372     } else {
00373       temp_serverName.type = PropertyValue::value;
00374     };
00375     // there's never a toolTip or whatsThis, so no need to clear them.
00376 
00377     return temp_serverName;
00378 }
00379 
00380 PropertyValue ripping::serverName() const
00381 {
00382   return internal_serverName;
00383 }
00384 
00385 void ripping::setServerName(const QString & newServerName)
00386 {
00387   if (internal_serverName.internalValue.toString() != newServerName) {
00388     internal_serverName = formatedServerName(newServerName);
00389     emit serverNameChanged(index(), internal_serverName);
00390   };
00391 }
00392 
00393 QString ripping::default_value_of_serverName()
00394 {
00395   return QString();
00396 }
00397 
00398 PropertyValue ripping::status() const
00399 {
00400   return internal_status;
00401 }
00402 
00403 PropertyValue ripping::formatedStatus(const statusType theStatus)
00404 {
00405     // variables
00406     PropertyValue temp_status;
00407 
00408     // code
00409     temp_status.internalValue.setValue(theStatus);
00410     if (theStatus == idle) {
00411       temp_status.type = PropertyValue::unset;
00412     } else {
00413       temp_status.type = PropertyValue::value;
00414     };
00415     switch (theStatus) {
00416       case idle:
00417         temp_status.formatedValue.clear();
00418         temp_status.toolTip.clear();
00419         temp_status.whatsThis.clear();
00420         break;
00421       case is_starting:
00422         temp_status.formatedValue = i18nc("@item status of streamripper", "Starting...");
00423         temp_status.toolTip = i18nc("@info:tooltip", "Invoking Streamripper...");
00424         temp_status.whatsThis = i18nc(
00425           "@info:whatsthis",
00426           "KStreamRipper is invoking Streamripper, the program used to perform the recording "
00427             "process.");
00428         break;
00429       case is_connecting:
00430         temp_status.formatedValue = i18nc("@item status of Streamripper", "Connecting...");
00431         temp_status.toolTip = i18nc("@info:tooltip", "Connecting with the stream server...");
00432         temp_status.whatsThis = i18nc("@info:whatsthis",
00433                                       "Streamripper is connecting with the stream server.");
00434         break;
00435       case is_buffering:
00436         temp_status.formatedValue = i18nc("@item status of Streamripper", "Buffering...");
00437         temp_status.toolTip = i18nc("@info:tooltip", "Buffering stream data...");
00438         temp_status.whatsThis = i18nc(
00439           "@info:whatsthis",
00440           "Stream data is buffered before starting the recording process.");
00441         break;
00442       case is_skipping:
00443         temp_status.formatedValue = i18nc("@item status of Streamripper", "Skipping...");
00444         temp_status.toolTip = i18nc("@info:tooltip", "Skipping the actual track...");
00445         temp_status.whatsThis = i18nc(
00446           "@info:whatsthis",
00447           "<para>KStreamRipper skips by default the first track, because this track will lack "
00448             "the begin.</para><para>You can change this behavior by editing the settings of the "
00449             "stream.</para>");
00450         break;
00451       case is_ripping:
00452         temp_status.formatedValue = i18nc("@item status of Streamripper", "Recording...");
00453         temp_status.toolTip = i18nc("@info:tooltip", "Recording the stream...");
00454         temp_status.whatsThis = i18nc("@info:whatsthis", "KStreamRipper is recording the stream.");
00455         break;
00456       case is_saving:
00457         temp_status.formatedValue = i18nc("@item status of Streamripper", "Saving...");
00458         temp_status.toolTip = i18nc("@info:tooltip", "Saving files...");
00459         temp_status.whatsThis = i18nc(
00460           "@info:whatsthis",
00461           "The buffer is processed and the last files are saved.");
00462         break;
00463     };
00464 
00465     return temp_status;
00466 }
00467 
00468 void ripping::setStatus(const statusType newStatus)
00469 {
00470   bool emitStatusChanged = false;
00471   bool emitRunning = false;
00472   bool emitNotRunning = false;
00473 
00474   if (internal_status.internalValue.value<statusType>() != newStatus) {
00475     internal_status = formatedStatus(newStatus);
00476     emitStatusChanged = true;
00477     refreshRelayPort();
00478   };
00479 
00480   bool newIsRunning = (newStatus != idle);
00481   if (internal_isRunning != newIsRunning) {
00482     internal_isRunning = newIsRunning;
00483     if (newIsRunning) {
00484       emitRunning = true;
00485     } else {
00486       emitRunning = true;
00487     };
00488   };
00489 
00490   // This is to make sure that, when we emit the signals, _all_ properties are yet updated.
00491   if (emitStatusChanged) {
00492     emit statusChanged(index(), internal_status);
00493   };
00494   if (emitRunning) {
00495     emit running();
00496   };
00497   if (emitNotRunning) {
00498     emit not_running();
00499   };
00500 }
00501 
00502 ripping::statusType ripping::default_value_of_status()
00503 {
00504   return idle;
00505 }
00506 
00507 PropertyValue ripping::error() const
00508 {
00509   return internal_error;
00510 }
00511 
00512 PropertyValue ripping::formatedError(const QString & theError)
00513 {
00514   // variables
00515   PropertyValue temp_error;
00516   QString streamripper_error_number;
00517   QString temp_message_of_unknown_id;
00518 
00519   // code
00520   temp_error.internalValue = theError;
00521 
00522   if (theError.isEmpty()) {
00523     temp_error.type = PropertyValue::unset;
00524   } else {
00525     temp_error.type = PropertyValue::value;
00526   };
00527 
00528   // message of format "error -123 bug description"
00529   if (theError.startsWith(QString("error -"))) {
00530     streamripper_error_number = theError.right(theError.size() - 7);  // without "error -"
00531     streamripper_error_number = streamripper_error_number.section(' ', 0, 0);
00532     switch (streamripper_error_number.toLongLong()) {
00533       case 3: // SR_ERROR_INVALID_URL
00534       case 6: // SR_ERROR_CANT_RESOLVE_HOSTNAME
00535         temp_error.formatedValue = i18nc("@item error message", "connection failed");
00536         temp_error.toolTip = i18nc("@info:tooltip error message", "Could not connect to server");
00537         temp_error.whatsThis = i18nc(
00538           "@info:whatsthis error message",
00539           "Either the URL is invalid or the corresponding server does not exist.");
00540         break;
00541       case 7: // SR_ERROR_RECV_FAILED
00542         temp_error.formatedValue = i18nc("@item error message", "incompatible stream");
00543         temp_error.toolTip = i18nc("@info:tooltip error message",
00544                                    "KStreamRipper can not record this type of stream.");
00545         temp_error.whatsThis = i18nc("@info:whatsthis error message",
00546                                       "Streamripper (and so also KStreamRipper) can only "
00547                                       "record shoutcast and icecast streams.");
00548         break;
00549       case 56: // HTTP:403 - Access Forbidden (try changing the UserAgent)
00550         temp_error.formatedValue = i18nc("@item error message", "connection refused");
00551         temp_error.toolTip = i18nc("@info:tooltip error message",
00552                                    "Try changing the user agent string");
00553         temp_error.whatsThis = i18nc(
00554           "@info:whatsthis error message",
00555           "<para>The server has refused the connection.</para><para>You can try to use another "
00556           "user agent string - maybe the server will accept this.</para>");
00557         break;
00558       case 64: // SR_ERROR_CANT_PARSE_PLS
00559         temp_error.formatedValue = i18nc("@item error message", "no stream");
00560         temp_error.toolTip = i18nc("@info:tooltip error message", "invalid playlist");
00561         temp_error.whatsThis = i18nc("@info:whatsthis error message",
00562                                      "The URL does not point directly to a stream, but to a "
00563                                        "playlist. And the playlist is invalid.");
00564         break;
00565       case 36: // SR_ERROR_CANT_CREATE_FILE
00566       case 1001: // BAD_DOWNLOAD_DIRECTORY
00567         temp_error.formatedValue = i18nc("@item error message", "bad download directory");
00568         temp_error.toolTip = i18nc(
00569           "@info:tooltip error message",
00570           "KStreamRipper could not write the file because the download directory is not writable.");
00571         temp_error.whatsThis = i18nc(
00572           "@info:whatsthis error message",
00573           "<para>The download directory is not accessible. Either "
00574             "it does not exist or you do not have sufficient access rights.</para><para>You "
00575             "can change the download directory at <emphasis>Settings</emphasis>, <emphasis>"
00576             "Configure KStreamRipper...</emphasis>, <emphasis>Saving</emphasis>"
00577             "</para>");
00578         break;
00579       default:
00580         temp_message_of_unknown_id = theError.right(theError.size() - 7);  // without "error -"
00581         temp_message_of_unknown_id = temp_message_of_unknown_id.section(' ', 1, 1);
00582         temp_error.formatedValue = i18nc("@item error message for an error that was not recognized "
00583                                            "(item 1: ID number of the error; item 2: error "
00584                                            "description in english",
00585                                          "Error %1: %2",
00586                                          streamripper_error_number.toLongLong(),
00587                                          temp_message_of_unknown_id);
00588         break;
00589     };
00590   } else {
00591     temp_error.formatedValue = theError;
00592   };
00593 
00594   return temp_error;
00595 }
00596 
00597 void ripping::setError(const QString & newError)
00598 {
00599   if ((internal_error.internalValue.toString() != newError)) {
00600     /* we can't use != directly because QVariant doesn't support
00601     this for custom data types (but also doesn't generate a
00602     compiler error). */
00603     internal_error = formatedError(newError);
00604     emit errorChanged(index(), internal_error);
00605     refreshRelayPort();
00606   };
00607 }
00608 
00609 QString ripping::default_value_of_error()
00610 {
00611   return QString();
00612 }
00613 
00614 PropertyValue ripping::song() const
00615 {
00616   return internal_song;
00617 }
00618 
00619 PropertyValue ripping::formatedSong(const QString & theSong)
00620 {
00621     // variables
00622     PropertyValue temp_song;
00623 
00624     // code
00625     temp_song.internalValue = theSong;
00626     if ((theSong.isEmpty()) OR (theSong EQUAL " - ")) {
00627       temp_song.type = PropertyValue::unset;
00628       temp_song.formatedValue.clear();
00629     } else {
00630       temp_song.type = PropertyValue::value;
00631       temp_song.formatedValue = theSong;
00632     };
00633     // there's never a toolTip or whatsThis, so no need to clear them.
00634     return temp_song;
00635 }
00636 
00637 void ripping::setSong(const QString & newSong)
00638 {
00639   if (internal_song.internalValue.toString() != newSong) {
00640     internal_song = formatedSong(newSong);
00641     emit songChanged(index(), internal_song);
00642   };
00643 }
00644 
00645 QString ripping::default_value_of_song()
00646 {
00647   return QString();
00648 }
00649 
00650 PropertyValue ripping::dataSize() const
00651 {
00652   return internal_dataSize;
00653 }
00654 
00655 PropertyValue ripping::formatedDataSize(const qint64 theDataSize)
00656 {
00657   // variables
00658   PropertyValue temp_dataSize;
00659 
00660   // code
00661   temp_dataSize.internalValue = theDataSize;
00662   if (theDataSize == (-1)) {
00663     temp_dataSize.type = PropertyValue::error;
00664     temp_dataSize.formatedValue = i18nc("@item", "error");
00665     temp_dataSize.toolTip = i18nc ("@info:tooltip", "Error determining track size");
00666     temp_dataSize.whatsThis = i18nc (
00667       "@info:whatsthis",
00668       "The track size could not be determined. Please report this as a bug.");
00669   } else {
00670     if (theDataSize >= 0) {
00671       temp_dataSize.type = PropertyValue::value;
00672       temp_dataSize.formatedValue = ki18nc("@item The unit is MiB instead of MB. See "
00673                                              "http://en.wikipedia.org/wiki/Binary_prefix "
00674                                              "for details.",
00675                                            "%1 MiB")
00676           .subs(double(theDataSize) / (1024 * 1024), 0, 'f', 2).toString();
00677       temp_dataSize.whatsThis = i18nc("@info:whatsthis",
00678                                        "<para>The size of the track in MiB.</para><para>MiB "
00679                                        "has a binary prefix which means 1024 * 1024 B = "
00680                                        "1048576 B (different from MB which would mean "
00681                                        "1000000 B).</para>");
00682     } else { // theDataSize < -1
00683       temp_dataSize.type = PropertyValue::unset;
00684       temp_dataSize.formatedValue.clear();
00685       temp_dataSize.whatsThis.clear();
00686     };
00687     temp_dataSize.toolTip.clear();
00688   };
00689   temp_dataSize.formatedValueAlignment = (Qt::AlignRight | Qt::AlignVCenter);
00690 
00691   return temp_dataSize;
00692 }
00693 
00694 void ripping::setDataSize(const qint64 newDataSize)
00695 {
00696   if (internal_dataSize.internalValue.toLongLong() != newDataSize) {
00697     internal_dataSize = formatedDataSize(newDataSize);
00698     emit dataSizeChanged(index(), internal_dataSize);
00699   };
00700 }
00701 
00702 qint64 ripping::default_value_of_dataSize()
00703 {
00704   return (-2);
00705 }
00706 
00707 bool ripping::default_value_of_isRunning()
00708 {
00709   return false;
00710 }
00711 
00712 bool ripping::isRunning() const
00713 {
00714   return internal_isRunning;
00715 }
00716 
00717 PropertyValue ripping::relayPort() const
00718 {
00719   return internal_relayPort;
00720 }
00721 
00722 PropertyValue ripping::formatedRelayPort(const qint64 theRelayPort)
00723 {
00724     // variables
00725     PropertyValue temp_relayPort;
00726 
00727     // code
00728     temp_relayPort.internalValue = theRelayPort;
00729     if (theRelayPort == (-1)) {
00730       temp_relayPort.type = PropertyValue::error;
00731       temp_relayPort.formatedValue = i18nc(
00732         "@item",
00733         "error");
00734       temp_relayPort.toolTip = i18nc (
00735         "@info:tooltip",
00736         "Error determining relay server port");
00737       temp_relayPort.whatsThis = i18nc (
00738         "@info:whatsthis",
00739         "The port of the relay server could not be determined. Please report this as a bug.");
00740     } else {
00741       if (theRelayPort >= 0) {
00742         temp_relayPort.type = PropertyValue::value;
00743         temp_relayPort.formatedValue = KGlobal::locale()->formatLong(theRelayPort);
00744       } else { // relayPort < -1
00745         temp_relayPort.type = PropertyValue::unset;
00746         temp_relayPort.formatedValue.clear();
00747       };
00748       temp_relayPort.toolTip.clear();
00749       temp_relayPort.whatsThis.clear();
00750     };
00751     temp_relayPort.formatedValueAlignment = (Qt::AlignRight | Qt::AlignVCenter);
00752 
00753     return temp_relayPort;
00754 }
00755 
00756 void ripping::refreshRelayPort()
00757 {
00758   statusType actualStatus = status().internalValue.value<statusType>();
00759   if ((actualStatus == is_skipping || actualStatus == is_ripping) &&
00760       (error().type == PropertyValue::unset)) {
00761     internal_relayPort = formatedRelayPort(lastRecognizedRelayPort);
00762   } else {
00763     internal_relayPort = formatedRelayPort(-2);
00764   };
00765   emit relayPortChanged(index(), internal_relayPort);
00766 }
00767 
00768 void ripping::setRelayPort(const qint64 newRelayPort)
00769 {
00770   if (lastRecognizedRelayPort != newRelayPort) {
00771     lastRecognizedRelayPort = newRelayPort;
00772     refreshRelayPort();
00773   };
00774 }
00775 
00776 qint64 ripping::default_value_of_relayPort()
00777 {
00778   return (-2);
00779 }
00780 
00781 PropertyValue ripping::bitrate() const
00782 {
00783   return internal_bitrate;
00784 }
00785 
00786 PropertyValue ripping::formatedBitrate(const qint64 theBitrate)
00787 {
00788     // variables
00789     PropertyValue temp_bitrate;
00790 
00791     // code
00792     temp_bitrate.internalValue=theBitrate;
00793 
00794     if (theBitrate >= 1) {  // a valid bitrate...
00795       temp_bitrate.formatedValue = i18ncp(
00796         "@item This makes a nicly formated string for the bitrate of a stream - %1 is an integer. "
00797           "WARNING: Unit has changed! It is now kbit instead of Kibit. "
00798           "This means 1000 bit (NOT 1024).",
00799         "%1 kbit/s",
00800         "%1 kbit/s",
00801         theBitrate);
00802       temp_bitrate.type = PropertyValue::value;
00803       temp_bitrate.toolTip = i18nc("@info:tooltip", "declared bit rate");
00804       temp_bitrate.whatsThis = i18nc(
00805         "@info:whatsthis WARNING Unit has changed from binary prefix to SI prefix",
00806         "<para>The declared bit rate of the stream in kbit/s.</para><para>kbit has an SI prefix "
00807           "which means 1000 bit (different from Kibit which would mean 1024 bit). So 1 kbit/s "
00808           "means 1000 bits per second.</para>");
00809     } else {
00810       if (theBitrate >= -1) {  // "0" or "-1" (error during recognization)
00811         temp_bitrate.formatedValue = i18nc(
00812           "@item This makes a nicly formated string for the bitrate of a stream.",
00813           "Unable to recognize bitrate.");
00814         temp_bitrate.type = PropertyValue::error;
00815         temp_bitrate.toolTip = i18nc (
00816           "@info:tooltip",
00817           "Error determining bit rate");
00818         temp_bitrate.whatsThis = i18nc (
00819           "@info:whatsthis",
00820           "The bit rate could not be determined. Please report this as a bug.");
00821       } else { // "-2" or smaller (no value set)
00822         temp_bitrate.formatedValue.clear();
00823         temp_bitrate.type = PropertyValue::unset;
00824         temp_bitrate.toolTip.clear();
00825         temp_bitrate.whatsThis.clear();
00826       }
00827     };
00828 
00829     temp_bitrate.formatedValueAlignment = (Qt::AlignRight | Qt::AlignVCenter);
00830 
00831     return temp_bitrate;
00832 }
00833 
00834 void ripping::setBitrate(const qint64 newBitrate)
00835 {
00836   if (internal_bitrate.internalValue.toLongLong() != newBitrate) {
00837     internal_bitrate = formatedBitrate(newBitrate);
00838     emit bitrateChanged(index(), internal_bitrate);
00839   };
00840 }
00841 
00842 qint64 ripping::default_value_of_bitrate()
00843 {
00844   return (-2);
00845 }
00846 
00847 PropertyValue ripping::metaInterval() const
00848 {
00849   return internal_metaInterval;
00850 }
00851 
00852 PropertyValue ripping::formatedMetaInterval(const qint64 theMetaInterval)
00853 {
00854     // variables
00855     PropertyValue temp_metaInterval;
00856 
00857     // code
00858     temp_metaInterval.internalValue = theMetaInterval;
00859     if (theMetaInterval > 0) {     // metaInterval is 1 or bigger
00860       temp_metaInterval.formatedValue = //KGlobal::locale()->formatLong(theMetaInterval);
00861         ki18nc("@item The unit is KiB instead of kB. See "
00862                  "http://en.wikipedia.org/wiki/Binary_prefix for details.",
00863                "%1 KiB")
00864           .subs(qint64(theMetaInterval / 1024)).toString();
00865       temp_metaInterval.type = PropertyValue::value;
00866     } else {
00867       if (theMetaInterval >= -1) { // metaInterval is 0 or -1
00868         temp_metaInterval.formatedValue = i18nc(
00869           "@item",
00870           "error");
00871         temp_metaInterval.type = PropertyValue::error;
00872       } else {
00873         temp_metaInterval.formatedValue.clear();
00874         temp_metaInterval.type = PropertyValue::unset;
00875       };
00876     };
00877     temp_metaInterval.formatedValueAlignment = (Qt::AlignRight | Qt::AlignVCenter);
00878 
00879     return temp_metaInterval;
00880 }
00881 
00882 void ripping::setMetaInterval(const qint64 newMetaInterval)
00883 {
00884   if (internal_metaInterval.internalValue != newMetaInterval) {
00885     internal_metaInterval = formatedMetaInterval(newMetaInterval);
00886     emit metaIntervalChanged(index(), internal_metaInterval);
00887   };
00888 }
00889 
00890 qint64 ripping::default_value_of_metaInterval()
00891 {
00892   return (-2);
00893 }
00894 
00895 PropertyValue ripping::metaInterval_milliSeconds() const
00896 {
00897   // variables
00898   qint64 temp;
00899 
00900   // code
00901   if (bitrate().type == PropertyValue::value &&  // both are values...
00902       metaInterval().type == PropertyValue::value &&
00903       bitrate().internalValue.toLongLong() > 0) { // Prevent from dividing by 0. Is redundant to
00904                                             // bitrate.type == PropertyValue::value.
00905                                             // Just to be sure, also with further changes...
00906     /* bitrate has the unit kbit/s...
00907     * kbit/s = (1000 bit)/s
00908     *        = (1000 bit)/(1000 ms)
00909     *        = bit/ms.
00910     * Conclusion: kbit/s=bit/ms, so we can think of bitrate as bit/ms.
00911     *
00912     * metaInterval has the unit Byte. So we take the value *8, so we are
00913     * in the unit bit.
00914     *
00915     * So we calculate: (metaInterval*8)/bitrate. This works fine for the units:
00916     * bit/(bit/ms)=ms, so we get the metaInterval in ms! */
00917     temp = (metaInterval().internalValue.toLongLong() * 8) / bitrate().internalValue.toLongLong();
00918   } else if (bitrate().type == PropertyValue::unset ||  // at least one is unset...
00919              metaInterval().type == PropertyValue::unset) {
00920     temp = -2;
00921   } else {
00922     temp = -1;
00923   };
00924   return formatedMetaInterval_milliSeconds(temp);
00925 }
00926 
00927 PropertyValue ripping::formatedMetaInterval_milliSeconds(const qint64 theMetaInterval)
00928 {
00929     // variables
00930     PropertyValue temp_metaInterval_milliSeconds;
00931 
00932     // code
00933     temp_metaInterval_milliSeconds.internalValue = theMetaInterval;
00934     if (theMetaInterval > 0) {     // metaInterval is 1 or bigger
00935       temp_metaInterval_milliSeconds.formatedValue =
00936         ki18nc("@item milliseconds", "%1 ms").subs(theMetaInterval).toString();
00937       temp_metaInterval_milliSeconds.type = PropertyValue::value;
00938     } else {
00939       if (theMetaInterval >= -1) { // metaInterval is 0 or -1
00940         temp_metaInterval_milliSeconds.formatedValue = i18nc(
00941           "@item",
00942           "error");
00943         temp_metaInterval_milliSeconds.type = PropertyValue::error;
00944       } else {
00945         temp_metaInterval_milliSeconds.formatedValue.clear();
00946         temp_metaInterval_milliSeconds.type = PropertyValue::unset;
00947       };
00948     };
00949     temp_metaInterval_milliSeconds.formatedValueAlignment = (Qt::AlignRight | Qt::AlignVCenter);
00950 
00951     return temp_metaInterval_milliSeconds;
00952 }
00953 
00954 void ripping::errorOccured(const QProcess::ProcessError error)
00955 {
00956   // variables
00957   QFileInfo m_file_info(settings_general::streamripperCommand());
00958 
00959   // code
00960   switch (error) {
00961     case QProcess::FailedToStart:
00962       if (m_file_info.exists()) {
00963         setError(i18nc(
00964           "@item streamripper error",
00965           "Insufficient permissions to invoke Streamripper."));
00966       } else {
00967         setError(i18nc("@item streamripper error", "Streamripper binary not found."));
00968       };
00969       break;
00970     case QProcess::Crashed:
00971       setError(i18nc("@item streamripper error", "Streamripper crashed."));
00972       break;
00973     case QProcess::Timedout:
00974       setError(i18nc("@item streamripper error", "Streamripper does not react."));
00975       break;
00976     default:  //ReadError, WriteError, UnknownError
00977       setError(i18nc("@item streamripper error", "Error accessing Streamripper."));
00978       break;
00979   };
00980 }
00981 
00982 void ripping::streamripperStateChange(const QProcess::ProcessState newState)
00983 {
00984   if (newState == QProcess::NotRunning) {
00985     setStatus(default_value_of_status());
00986     setSong(default_value_of_song());
00987     /* Here, we can't determinate
00988     *  - if the program has terminated normally (good) or
00989     *  - if it has crashed or
00990     *  - if it couldn't even be started, because the binary wasn't found or
00991     *    wasn't executable.
00992     *
00993     *  In the last 2 cases, we should display an error message, depending on
00994     *  _which_ error occurred. But we can't use m_process.error() to determinate
00995     *  the error type, because QProcess emits the signal stateChanged() (to which
00996     *  this slot is connected) _before_ it actualizes the property error().
00997     *  Because of this, the error message is set by the slot errorOccured(), who
00998     *  is connected to the signal error(). */
00999     setDataSize(default_value_of_dataSize());
01000     setRelayPort(default_value_of_relayPort());
01001   };
01002 }
01003 
01004 QStringList ripping::parameterList() const
01005 {
01006   // variables
01007   QStringList parameters;
01008   QString temp;
01009 
01010   //code
01011   parameters.append(serverUri());
01012 
01013   parameters.append(QString("-t")); // don't override files in the incomplete dir
01014                                     // but make instead a save copy of the old file
01015                                     // by appending an index. I'm not sure if this
01016                                     // is absolutly needed, but at least it's not bad.
01017 
01018   temp = proxyinfo::proxyserver(serverUri()).at(0);
01019   if (temp != "direct://") {
01020     parameters.append(QString("-p"));
01021     parameters.append(temp);
01022   };
01023 
01024   if (settings_general::createRelayServer()) { // create relay server
01025     // create a relay server at (or up to) port X!
01026     parameters.append(QString("-r"));
01027     parameters.append(QString::number(settings_general::preferedPortForRelayServer()));
01028 
01029     // the number of connections is limited to which number?
01030     parameters.append(QString("-R"));
01031     if (settings_general::limitConnections()) {
01032       parameters.append(QString::number(settings_general::limitConnectionsToX()));
01033     } else {
01034       parameters.append(QString("0"));
01035     };
01036   };
01037 
01038   parameters.append(QString("-c"));  // don't auto-reconnect  TODO: implement this on my own way!
01039 
01040   return parameters;
01041 }
01042 
01043 QString ripping::streamripperCommand() const
01044 {
01045   return settings_general::streamripperCommand();
01046 }
01047 
01048 void ripping::startStreamripper()
01049 {
01050   // variables
01051   QFileInfo * dir;
01052   QDir *dirCreator;
01053   QString temp;
01054 
01055   // code
01056   if (m_process.state() == QProcess::NotRunning) { // TODO else if the process is
01057     // yet shutting down or has yet an error -> einreihen für späteren automatischen Neustart!
01058     setError(QString());
01059     setStatus(is_starting);
01060     temp = workingDirectory();
01061     if (temp.startsWith(QLatin1String("file://"))) {
01062       temp.remove(0, 7);
01063     };
01064     dir = new QFileInfo(temp);
01065     // If dir doesn't exist and is an absulute path: Create it (if possible).
01066     if ((!dir->exists()) && dir->isAbsolute()) {
01067       dirCreator = new QDir(/*dir*/);
01068       dirCreator->mkpath(dir->filePath());
01069       dir->refresh();
01070     };
01071     if (dir->exists() && dir->isAbsolute() && dir->isDir() &&
01072         dir->isReadable() && dir->isWritable()) {
01073       m_process.setWorkingDirectory(workingDirectory());
01074       streamripper_base::startStreamripper();
01075     } else {
01076       // Emulate streamripper error message to get automatically a formated message:
01077       setError("error -1001 [BAD_DOWNLOAD_DIRECTORY]");
01078       setStatus(idle);
01079     };
01080   };
01081 }
01082 
01083 void ripping::shutDown()
01084 {
01085   m_process.terminate();
01086   /* TODO eventuelle Einreihung für Neustart wieder löschen! (wenn jemand startet,
01087   während gerade runtergefahren wird) */
01088 }
01089 
01090 bool ripping::doesTheUserWantsThatTheStreamIsRipping(const ripping::statusType theStatus)
01091 {
01092   return (!((theStatus == ripping::idle) OR (theStatus == ripping::is_saving)));
01093 }
01094 
01095 bool ripping::doesTheUserWantsThatTheStreamIsRipping() const
01096 {
01097   return doesTheUserWantsThatTheStreamIsRipping(
01098            internal_status.internalValue.value<statusType>());
01099 }

doxygen