[svn-upgrade] Integrating new upstream version, teleschorsch (0.1.5)
[debian/teleschorsch.git] / main.cpp
1 /*
2 # Copyright and License:
3 #
4 # Copyright (C) 2007 
5 # gregor herrmann <gregor+debian@comodo.priv.at>,
6 # Philipp Spitzer <philipp@spitzer.priv.at>
7 #
8 # This program is free software; you can redistribute it and/or modify it   
9 # under the terms of the GNU General Public License version 2 as published
10 # by the Free Software Foundation.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, or point
18 # your browser to http://www.gnu.org/licenses/gpl.html
19 */
20
21 #include <QtGlobal>
22 #include <QtDebug>
23 #include <QApplication>
24 #include <QVector>
25 #include <QFile>
26 #include <QProcess>
27 #include <QMessageBox>
28 #include <QDateTime>
29 #include <QLocale>
30 #include <QDir>
31 #include <QTranslator>
32
33 #include <iostream> // for help output
34 #include <string>   // for help output
35
36 #include "main.h"
37 #include "options.h"
38
39
40
41 // Functions
42 // =========
43
44 void initConfigInfo(ConfigInfo& configInfo) {
45         configInfo.userConfigFile = QDir::homePath() + "/.teleschorschrc";
46         configInfo.systemConfigFile = "/etc/teleschorschrc";
47
48         // User config file
49         if (!configInfo.userConfigFile.isEmpty()) {
50                 QFile configFileUser(configInfo.userConfigFile);
51                 if (configFileUser.exists()) {configInfo.usedConfigFile  = configInfo.userConfigFile;}
52         }
53
54         // System config file
55         if (configInfo.usedConfigFile.isEmpty()) {
56                 QFile configFileSystem(configInfo.systemConfigFile);
57                 if (configFileSystem.exists()) configInfo.usedConfigFile = configInfo.systemConfigFile;
58         }
59 }
60
61
62 bool addConfig(const QString& fileName, ChannelVec& cv, QString& error) {
63         QFile file(fileName);
64         if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {error = QObject::tr("Could not open file %1").arg(fileName); return false;}
65         Channel channel; // current channel
66         bool sectionOccured = false;
67         while (!file.atEnd()) {
68                 QString line = QString(file.readLine()).trimmed();
69                 if (line.startsWith("#") || line.startsWith(";") || line.isEmpty()) continue; // strip comments
70                 
71                 // Is the line a [section]?
72                 bool section = line.startsWith("[") && line.endsWith("]") && line.size() >= 3;
73                 if (section) {
74                         if (sectionOccured) cv.push_back(channel);
75                         sectionOccured = true;
76                         channel = Channel();
77                         channel.name = line.mid(1, line.size()-2);
78                 }
79
80                 // Read properties
81                 int eqPos = line.indexOf("=");
82                 bool property = !section && eqPos >= 1;
83                 if (property) {
84                         QString propKey = line.left(eqPos);
85                         QString propVal = line.right(line.size()-eqPos-1);
86                         if (!sectionOccured) {error = QObject::tr("Property %1 is only allowed in a [section].").arg(propKey); return false;}
87
88                         if (propKey == "FULLNAME") channel.fullName = propVal;
89                         else if (propKey == "STATICURL") channel.staticUrl = propVal;
90                         else if (propKey == "PLAYER") channel.player = propVal;
91                         else {error = QObject::tr("Unknown key in ini file: %1").arg(propKey); return false;}
92                 }
93
94                 if (!section && !property) {error = QObject::tr("Line %1 is not valid.").arg(line); return false;}
95         }
96         if (sectionOccured) cv.push_back(channel);
97         return true;
98 }
99
100
101 QString readChannelVec(const ConfigInfo& configInfo, ChannelVec& channelVec) {
102         if (configInfo.usedConfigFile.isEmpty()) return QObject::tr("Neither %1 nor %2 found.").arg(configInfo.systemConfigFile).arg(configInfo.userConfigFile);
103         QString error;
104         if (!addConfig(configInfo.usedConfigFile, channelVec, error));
105         return error;
106 }
107
108
109 /// \brief Finds a value for a specified variable and appends it to a string.
110 ///
111 /// \param[in]  var    variable to substitute. These are the possibilities:
112 ///                    - d  day of month (01-31)
113 ///                    - m  month (01-12)
114 ///                    - y  year (last two digits)
115 ///                    - Y  year (4 digits)
116 ///                    - dow_DE  day of week in German (Montag, ...)
117 /// \param[in]  date   date that should be used when replacing the date dependend variables
118 /// \param[out] result The determined value of the variable is _appended_.
119 /// \param[out] error  error message in error cases
120 /// \returns true in case of success.
121 bool substituteVar(const QString& var, QDate date, QString& result, QString& errorMsg) {
122         if (var == "d") {result += date.toString("dd"); return true;}
123         if (var == "m") {result += date.toString("MM"); return true;}
124         if (var == "y") {result += date.toString("yy"); return true;}
125         if (var == "Y") {result += date.toString("yyyy"); return true;}
126         if (var == "dow_DE") {
127                 int dow = date.dayOfWeek() - 1;
128                 static const char dow_de[][16] = {"Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"};
129                 if (dow >= 0 && dow < 7) {result += dow_de[dow]; return true;}
130         }
131         errorMsg = QObject::tr("No match for variable %1").arg(var);
132         return false;
133 }
134
135
136 /// \brief Finds a string, where variables of the form <code>${var}</code> are substituted in input (normally a staticUrl).
137 ///
138 /// - ${d}  day of month (01-31)
139 /// - ${m}  month (01-12)
140 /// - ${y}  year (last two digits)
141 /// - ${Y}  year (4 digits)
142 /// - ${dow_DE}  day of week in German (Montag, ...)
143 ///
144 /// \param[in]  input  string where the substitution should be done.
145 /// \param[in]  date   date that should be used when replacing the date dependend variables
146 /// \param[out] result The result string with the variables substituted is appended here
147 /// \param[out] error  error message in error cases
148 /// \returns true in case of success.
149 bool substituteVars(const QString& input, QDate date, QString& result, QString& errorMsg) {
150         int pos = 0;
151         int lastPos = 0;
152         while (pos != -1) {
153                 pos = input.indexOf("${", lastPos);
154                 if (pos == -1) result += input.mid(lastPos);
155                 else result += input.mid(lastPos, pos-lastPos);
156                 lastPos = pos;
157                 
158                 // Match?
159                 if (pos != -1) {
160                         pos = input.indexOf("}", lastPos);
161                         if (pos == -1) {
162                                 errorMsg = QObject::tr("${ not closed with }.");
163                                 return false;
164                         }
165                         QString var = input.mid(lastPos+2, pos-lastPos-2);
166                         if (!substituteVar(var, date, result, errorMsg)) return false;
167                         lastPos = pos+1;
168                 }
169         }
170         return true;
171 }
172
173
174 /// \brief Calls the /bin/sh shell with the specified command and appends the result to a string variable.
175 ///
176 /// \param[in]  command command to execute with <code>/bin/sh -c "command"</code>
177 /// \param[out] result  The result string where the output is appended at.
178 /// \param[out] error   error message in error cases
179 /// \returns true in case of success.
180 bool executeShellCommand(const QString& command, QString& result, QString& errorMsg) {
181         QProcess evalUrl(0);
182         QString cmd = "/bin/sh -c \"" + command + "\"";
183         evalUrl.start(cmd);
184         if (evalUrl.waitForFinished(3000)) {
185                 QByteArray newResult = evalUrl.readAllStandardOutput();
186                 if (result != newResult) {result += newResult.trimmed();}
187                 return true;
188         } 
189         errorMsg = QObject::tr("Shell command executed when substituting URL (%1) timed out.").arg(command);
190         return false;
191 }
192
193
194 /// \brief Evaluates the staticUrl
195 ///
196 /// - Variables of the form ${var} are substituted according the the function ::substituteVars
197 /// - If the staticUrl is enclosed by backticks it is evaluated by a shell command (after the substitution mentioned above)
198 ///
199 /// \param[in]  staticUrl string where the substitution should be done.
200 /// \param[in]  date      date that should be used when replacing the date dependend variables
201 /// \param[out] result    The result string is appended here
202 /// \param[out] error     error message in error cases
203 /// \returns true in case of success.
204 bool evaluateStaticUrl(const QString& staticUrl, QDate date, QString& result, QString& errorMsg) {
205         QString subst;
206         bool success = substituteVars(staticUrl, date, subst, errorMsg);
207         if (!success) return false;
208
209         // Evaluate staticUrl - it might be dynamic despite its name :-)
210         QString cmdres;
211         if (subst.left(1) == "`" && subst.right(1) == "`") {
212                 success = executeShellCommand(subst.mid(1, subst.size()-2), cmdres, errorMsg);
213                 if (!success) return false;
214                 result = cmdres;
215         } else result = subst;
216         return true;
217 }
218
219
220 void appendPlayerOptions(const QString& player, QTime offset, QStringList& arguments) {
221         QTime zero = QTime(0, 0, 0, 0);
222         int offsetSec = zero.secsTo(offset);
223         if (player.contains("vlc")) arguments.append("--start-time");
224         if (player.contains("gmplayer")) {
225                 arguments << "-cache" << "512";
226                 arguments << "-prefer-ipv4";    // -"- avoids IPV6 error message
227                 arguments << "-ss"; 
228         }
229         arguments << QString::number(offsetSec);
230 }
231
232
233 void showHelp() {
234         // Isn't there a way to output QStrings directly?
235         std::cout << QObject::tr("Usage: qteleschorsch [options]").toStdString() << std::endl
236         << QObject::tr("Options: --help: Shows this message at the command line and exits qteleschorsch").toStdString() << std::endl
237         << QObject::tr("More help: man qteleschorsch").toStdString() << std::endl;
238 }
239
240
241
242 // MainDialog
243 // ==========
244
245 MainDialog::MainDialog(QWidget *parent): QDialog(parent) {
246         // User interface
247         setupUi(this);
248         QObject::connect(btnOptions, SIGNAL(clicked()), this, SLOT(editOptions()));
249         QObject::connect(btnStart, SIGNAL(clicked()), this, SLOT(startAction()));
250
251         // Init configInfo
252         initConfigInfo(configInfo);
253
254         // Read config
255         QString error = readChannelVec(configInfo, channelVec);
256         if (!error.isEmpty()) QMessageBox::warning(this, tr("Problem when reading the configuration file"), error);
257
258         // Fill in channels
259         updateLwChannels();
260
261         // Default date
262         QDateTime dateTime = QDateTime::currentDateTime(); // set the default date to today if it is past midday
263         if (dateTime.time().hour() < 12) dateTime = dateTime.addDays(-1);
264         calDate->setSelectedDate(dateTime.date());
265 }
266
267
268 void MainDialog::editOptions() {
269         OptionsDialog *od = new OptionsDialog();
270         if (od->exec(configInfo.userConfigFile)) {
271                 channelVec.clear();
272                 QString error = readChannelVec(configInfo, channelVec);
273                 if (!error.isEmpty()) QMessageBox::warning(this, tr("Problem when reading the configuration file"), error);
274                 updateLwChannels();
275         }
276         delete od;
277 }
278
279
280 void MainDialog::updateLwChannels() {
281         int row = lwChannels->currentRow(); // remember selected row
282         lwChannels->clear();
283         for (int i = 0; i != channelVec.size(); ++i) lwChannels->addItem(channelVec[i].fullName);
284         if (row > lwChannels->count()) row = lwChannels->count()-1; // set to last row. if count==0, row gets -1.
285         if (row == -1 && lwChannels->count() > 0) row = 0; // set to first if nothing was selected previously.
286         if (row != -1) lwChannels->setCurrentRow(row);
287 }
288
289
290 bool MainDialog::startAction() {
291         int row = lwChannels->currentRow();
292         if (row >= 0) {
293                 Channel channel = channelVec[row];
294                 QDate date = calDate->selectedDate();
295                 QString substUrl;
296                 QString errorMsg;
297                 if (!evaluateStaticUrl(channel.staticUrl, date, substUrl, errorMsg)) {
298                         QMessageBox::warning(this, tr("Problem when substituting URL"), errorMsg);
299                         return false;
300                 }
301                 QStringList arguments;
302                 arguments.append(substUrl);
303                 appendPlayerOptions(channel.player, teOffset->time(), arguments);
304                 QProcess player(this);
305                 qDebug() << channel.player << arguments;
306                 player.startDetached(channel.player, arguments);
307                 return true;
308         }
309         return false;
310 }
311
312
313
314 // Main function
315 // =============
316
317 int main(int argc, char *argv[]) {
318         QApplication app(argc, argv);
319
320         // Initialize translation
321         QString locale = QLocale::system().name();
322         QTranslator translator;
323         translator.load(QString(":/qteleschorsch_") + locale);
324         app.installTranslator(&translator);
325
326         // Parse command line options
327         if (app.arguments().size() > 1) {
328                 showHelp();
329                 return 0;
330         }
331
332         MainDialog *mainDialog = new MainDialog();
333         mainDialog->show();
334         return app.exec();
335