0c72f6d402d75f7d1e116ef820a5f8392ec79866
[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 "main.h"
29 #include "options.h"
30
31
32 // Functions
33 // =========
34
35 void initConfigInfo(ConfigInfo& configInfo) {
36         QStringList environment = QProcess::systemEnvironment().filter(QRegExp("^HOME=")); // ("HOME=/home/gregoa")
37         if (environment.size() == 1) {
38                 configInfo.home = environment.at(0).mid(5);
39                 configInfo.userConfigFile = configInfo.home + + "/.teleschorschrc";
40         }
41         configInfo.systemConfigFile = "/etc/teleschorschrc";
42
43         // User config file
44         if (!configInfo.userConfigFile.isEmpty()) {
45                 QFile configFileUser(configInfo.userConfigFile);
46                 if (configFileUser.exists()) {configInfo.usedConfigFile  = configInfo.userConfigFile;}
47         }
48
49         // System config file
50         if (configInfo.usedConfigFile.isEmpty()) {
51                 QFile configFileSystem(configInfo.systemConfigFile);
52                 if (configFileSystem.exists()) configInfo.usedConfigFile = configInfo.systemConfigFile;
53         }
54 }
55
56
57 bool addConfig(const QString& fileName, ChannelVec& cv, QString& error) {
58         QFile file(fileName);
59         if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {error = QObject::tr("Could not open file %1").arg(fileName); return false;}
60         Channel channel; // current channel
61         bool sectionOccured = false;
62         while (!file.atEnd()) {
63                 QString line = QString(file.readLine()).trimmed();
64                 if (line.startsWith("#") || line.startsWith(";") || line.isEmpty()) continue; // strip comments
65                 
66                 // Is the line a [section]?
67                 bool section = line.startsWith("[") && line.endsWith("]") && line.size() >= 3;
68                 if (section) {
69                         if (sectionOccured) cv.push_back(channel);
70                         sectionOccured = true;
71                         channel = Channel();
72                         channel.name = line.mid(1, line.size()-2);
73                 }
74
75                 // Read properties
76                 int eqPos = line.indexOf("=");
77                 bool property = !section && eqPos >= 1;
78                 if (property) {
79                         QString propKey = line.left(eqPos);
80                         QString propVal = line.right(line.size()-eqPos-1);
81                         if (!sectionOccured) {error = QObject::tr("Property %1 is only allowed in a [section].").arg(propKey); return false;}
82
83                         if (propKey == "FULLNAME") channel.fullName = propVal;
84                         else if (propKey == "STATICURL") channel.staticUrl = propVal;
85                         else if (propKey == "PLAYER") channel.player = propVal;
86                         else {error = QObject::tr("Unknown key in ini file: %1").arg(propKey); return false;}
87                 }
88
89                 if (!section && !property) {error = QObject::tr("Line %1 is not valid.").arg(line); return false;}
90         }
91         if (sectionOccured) cv.push_back(channel);
92         return true;
93 }
94
95
96 QString readChannelVec(const ConfigInfo& configInfo, ChannelVec& channelVec) {
97         if (configInfo.usedConfigFile.isEmpty()) return QObject::tr("Neither %1 nor %2 found.").arg(configInfo.systemConfigFile).arg(configInfo.userConfigFile);
98         QString error;
99         if (!addConfig(configInfo.usedConfigFile, channelVec, error));
100         return error;
101 }
102
103
104 /// \brief Finds a value for a specified variable and appends it to a string.
105 ///
106 /// \param[in]  var    variable to substitute. These are the possibilities:
107 ///                    - d  day of month (01-31)
108 ///                    - m  month (01-12)
109 ///                    - y  year (last two digits)
110 ///                    - Y  year (4 digits)
111 ///                    - dow_DE  day of week in German (Montag, ...)
112 /// \param[in]  date   date that should be used when replacing the date dependend variables
113 /// \param[out] result The determined value of the variable is _appended_.
114 /// \param[out] error  error message in error cases
115 /// \returns true in case of success.
116 bool substituteVar(const QString& var, QDate date, QString& result, QString& errorMsg) {
117         if (var == "d") {result += date.toString("dd"); return true;}
118         if (var == "m") {result += date.toString("MM"); return true;}
119         if (var == "y") {result += date.toString("yy"); return true;}
120         if (var == "Y") {result += date.toString("yyyy"); return true;}
121         if (var == "dow_DE") {
122                 int dow = date.dayOfWeek() - 1;
123                 static const char dow_de[][16] = {"Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"};
124                 if (dow >= 0 && dow < 7) {result += dow_de[dow]; return true;}
125         }
126         errorMsg = QObject::tr("No match for variable %1").arg(var);
127         return false;
128 }
129
130
131 /// \brief Finds a string, where variables of the form <code>${var}</code> are substituted in input (normally a staticUrl).
132 ///
133 /// - ${d}  day of month (01-31)
134 /// - ${m}  month (01-12)
135 /// - ${y}  year (last two digits)
136 /// - ${Y}  year (4 digits)
137 /// - ${dow_DE}  day of week in German (Montag, ...)
138 ///
139 /// \param[in]  input  string where the substitution should be done.
140 /// \param[in]  date   date that should be used when replacing the date dependend variables
141 /// \param[out] result The result string with the variables substituted is appended here
142 /// \param[out] error  error message in error cases
143 /// \returns true in case of success.
144 bool substituteVars(const QString& input, QDate date, QString& result, QString& errorMsg) {
145         int pos = 0;
146         int lastPos = 0;
147         while (pos != -1) {
148                 pos = input.indexOf("${", lastPos);
149                 if (pos == -1) result += input.mid(lastPos);
150                 else result += input.mid(lastPos, pos-lastPos);
151                 lastPos = pos;
152                 
153                 // Match?
154                 if (pos != -1) {
155                         pos = input.indexOf("}", lastPos);
156                         if (pos == -1) {
157                                 errorMsg = QObject::tr("${ not closed with }.");
158                                 return false;
159                         }
160                         QString var = input.mid(lastPos+2, pos-lastPos-2);
161                         if (!substituteVar(var, date, result, errorMsg)) return false;
162                         lastPos = pos+1;
163                 }
164         }
165         return true;
166 }
167
168
169 /// \brief Calls the /bin/sh shell with the specified command and appends the result to a string variable.
170 ///
171 /// \param[in]  command command to execute with <code>/bin/sh -c "command"</code>
172 /// \param[out] result  The result string where the output is appended at.
173 /// \param[out] error   error message in error cases
174 /// \returns true in case of success.
175 bool executeShellCommand(const QString& command, QString& result, QString& errorMsg) {
176         QProcess evalUrl(0);
177         QString cmd = "/bin/sh -c \"" + command + "\"";
178         evalUrl.start(cmd);
179         if (evalUrl.waitForFinished(3000)) {
180                 QByteArray newResult = evalUrl.readAllStandardOutput();
181                 if (result != newResult) {result += newResult.trimmed();}
182                 return true;
183         } 
184         errorMsg = QObject::tr("Shell command executed when substituting URL (%1) timed out.").arg(command);
185         return false;
186 }
187
188
189 /// \brief Evaluates the staticUrl
190 ///
191 /// - Variables of the form ${var} are substituted according the the function ::substituteVars
192 /// - If the staticUrl is enclosed by backticks it is evaluated by a shell command (after the substitution mentioned above)
193 ///
194 /// \param[in]  staticUrl string where the substitution should be done.
195 /// \param[in]  date      date that should be used when replacing the date dependend variables
196 /// \param[out] result    The result string is appended here
197 /// \param[out] error     error message in error cases
198 /// \returns true in case of success.
199 bool evaluateStaticUrl(const QString& staticUrl, QDate date, QString& result, QString& errorMsg) {
200         QString subst;
201         bool success = substituteVars(staticUrl, date, subst, errorMsg);
202         if (!success) return false;
203
204         // Evaluate staticUrl - it might be dynamic despite its name :-)
205         QString cmdres;
206         if (subst.left(1) == "`" && subst.right(1) == "`") {
207                 success = executeShellCommand(subst.mid(1, subst.size()-2), cmdres, errorMsg);
208                 if (!success) return false;
209                 result = cmdres;
210         } else result = subst;
211         return true;
212 }
213
214
215 void appendPlayerOffsetOption(const QString& player, QTime offset, QStringList& arguments) {
216         QTime zero = QTime(0, 0, 0, 0);
217         int offsetSec = zero.secsTo(offset);
218         if (player.indexOf("vlc") != -1) arguments.append("--start-time");
219         if (player.indexOf("gmplayer") != -1) {
220                 arguments << "--cache" << "512"; // --cache 512 does not belong to this function but for now...
221                 arguments << "-ss"; 
222         }
223         arguments << QString::number(offsetSec);
224 }
225
226
227
228 // MainDialog
229 // ==========
230
231 MainDialog::MainDialog(QWidget *parent): QDialog(parent) {
232         // User interface
233         setupUi(this);
234         QObject::connect(btnOptions, SIGNAL(clicked()), this, SLOT(editOptions()));
235         QObject::connect(btnStart, SIGNAL(clicked()), this, SLOT(startAction()));
236
237         // Init configInfo
238         initConfigInfo(configInfo);
239
240         // Read config
241         QString error = readChannelVec(configInfo, channelVec);
242         if (!error.isEmpty()) QMessageBox::warning(this, tr("Problem when reading the configuration file"), error);
243
244         // Fill in channels
245         updateLwChannels();
246 }
247
248
249 void MainDialog::editOptions() {
250         OptionsDialog *od = new OptionsDialog();
251         if (od->exec(configInfo.userConfigFile)) {
252                 channelVec.clear();
253                 QString error = readChannelVec(configInfo, channelVec);
254                 if (!error.isEmpty()) QMessageBox::warning(this, tr("Problem when reading the configuration file"), error);
255                 updateLwChannels();
256         }
257         delete od;
258 }
259
260
261 void MainDialog::updateLwChannels() {
262         lwChannels->clear();
263         for (int i = 0; i != channelVec.size(); ++i) lwChannels->addItem(channelVec[i].fullName);
264 }
265
266
267 bool MainDialog::startAction() {
268         int row = lwChannels->currentRow();
269         if (row > 0) {
270                 Channel channel = channelVec[row];
271                 QDate date = calDate->selectedDate();
272                 QString substUrl;
273                 QString errorMsg;
274                 if (!evaluateStaticUrl(channel.staticUrl, date, substUrl, errorMsg)) {
275                         QMessageBox::warning(this, tr("Problem when substituting URL"), errorMsg);
276                         return false;
277                 }
278                 QStringList arguments;
279                 arguments.append(substUrl);
280                 appendPlayerOffsetOption(channel.player, teOffset->time(), arguments);
281                 QProcess player(this);
282                 qDebug() << channel.player << arguments;
283                 player.start(channel.player, arguments);
284                 player.waitForFinished(-1);
285                 return true;
286         }
287         return false;
288 }
289
290
291
292 // Main function
293 // =============
294
295 int main(int argc, char *argv[]) {
296         QApplication app(argc, argv);
297         MainDialog *mainDialog = new MainDialog();
298         mainDialog->show();
299         return app.exec();
300