Statistics
| Branch: | Tag: | Revision:

dvbd / src / scheduler.cpp @ e039900c

History | View | Annotate | Download (7.5 KB)

1
/*
2
  Copyright 2003 John Knottenbelt
3
  
4
  This program is free software; you can redistribute it and/or modify
5
  it under the terms of the GNU General Public License as published by
6
  the Free Software Foundation; either version 2 of the License, or
7
  (at your option) any later version.
8
 
9
  This program is distributed in the hope that it will be useful,
10
  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
  GNU General Public License for more details.
13
 
14
  You should have received a copy of the GNU General Public License
15
  along with this program; if not, write to the Free Software
16
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
17
*/
18

    
19
#include "scheduler.h"
20
#include "select.h"
21
#include "connection.h"
22
#include "connectionmanager.h"
23
#include "tunermanager.h"
24
#include "faketunermanager.h"
25
#include "utils.h"
26
#include "stringutil.h"
27

    
28
#include <sys/types.h>
29
#include <sys/stat.h>
30
#include <fcntl.h>
31

    
32
#include <cerrno>
33
#include <cstring>
34
#include <fstream>
35
#include <ctime>
36
#include <algorithm>
37

    
38
Scheduler::Scheduler(const std::string &dbfile, TunerManager *tm, ConnectionManager *cm)
39
  : lastJob(0), tm(tm), cm(cm), dbfile(dbfile)
40
{
41
  readDB();
42
}
43

    
44
Scheduler::~Scheduler()
45
{
46
}
47

    
48
void Scheduler::add( Recording &r )
49
{
50
  r.setJob(lastJob++);
51

    
52
  // Insert into the list sorted on start time
53
  for (List::iterator i = recordings.begin(); i != recordings.end(); i++) {
54
    if (r.getStartTime() < i->getStartTime()) {
55
      recordings.insert(i, r);
56
      writeDB();
57
      return;
58
    }
59
  }
60
  recordings.push_back(r);
61
  writeDB();
62
}
63

    
64
void Scheduler::readDB()
65
{
66
  // NB: We assume that the database is stored in sorted order.
67
  //     (this is true if it was written by writeDB)
68
  std::ifstream i(dbfile.c_str());
69
  if (!i) {
70
    std::cerr << "Warning: failed to open database file " << dbfile << " for reading:\n"
71
              << strerror(errno) << "\n";
72
    writeDB();
73
  }
74
    
75
  Recording r;
76
  while (i >> r) {
77
    recordings.push_back(r);
78
    lastJob = std::max(r.getJob() + 1, lastJob);
79
  }  
80
}
81

    
82
void Scheduler::writeDB() const
83
{
84
  // Write the db file
85
  std::ofstream o(dbfile.c_str());
86
  if (!o) {
87
    std::cerr << "Warning: failed to open database file " << dbfile << " for writing:\n"
88
              << strerror(errno) << "\n";
89
    return;
90
  }
91

    
92
  for (List::const_iterator i = recordings.begin(); i != recordings.end(); i++) 
93
    o << *i << "\n";
94

    
95
  o.close();
96
}
97

    
98

    
99
void Scheduler::addSelectFDs(Select &s) const
100
{
101
  long tv_sec, tv_usec;
102
  if (timeUntilNextRecording(tv_sec, tv_usec))
103
    s.addAlarm(tv_sec, tv_usec);
104
}
105

    
106
bool Scheduler::isReady(const Select &) const
107
{
108
  long tv_sec, tv_usec;
109
  if (timeUntilNextRecording(tv_sec, tv_usec))
110
    return tv_sec == 0 && tv_usec == 0;
111
  return false;
112
}
113

    
114
bool Scheduler::timeUntilNextRecording( long &tv_sec, long &tv_usec ) const
115
{
116
  time_t now = time(NULL);
117

    
118
  if (recordings.empty())
119
    return false;
120

    
121
  const Recording &r = *recordings.begin();
122

    
123
  tv_usec = 0;
124
  tv_sec = std::max(0L, r.getStartTime() - now);
125
  return true;
126
}
127

    
128
void Scheduler::process()
129
{
130
  long wait_sec, wait_usec;
131
  if (timeUntilNextRecording(wait_sec, wait_usec) &&
132
      wait_sec == 0 && wait_usec == 0) {
133
    Recording r = *recordings.begin();
134

    
135
    std::cout << "Scheduled recording\n"
136
              << r << std::endl;
137

    
138
    startRecording(r);
139

    
140
    recordings.pop_front();
141
    writeDB();
142
  }
143
}
144

    
145

    
146
void Scheduler::startRecording(const Recording &r)
147
{
148
  if (!accessOk(r.getPath(), r.getUid())) {
149
    std::cerr << "Insufficient access to " << r.getPath() << "\n";
150
    return;
151
  }
152

    
153
  // Don't overwrite existing files
154
  std::string path = r.getPath();
155
  int n = 0;
156
  while (access(path.c_str(), F_OK) == 0)
157
    path = r.getPath() + "." + toString(++n);
158
  
159
  int openMode = O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE;
160

    
161
  // Don't bother caching if possible
162
#ifdef O_STREAMING
163
  openMode |= O_STREAMING;
164
#endif
165

    
166
  int dataFD = open(r.getPath().c_str(), openMode, 0644);
167
  if (dataFD == -1) {
168
    std::cerr << "Failed to open file " << r.getPath() << " for writing: " 
169
              << strerror(errno) << "\n";
170
  }
171

    
172
  Connection *c = new Connection( tm, dataFD );
173
  c->setStopTime( r.getStopTime() );
174
  c->tune(r.getType(), r.getChannel(), r.getConvertToPS(), r.getPriority());
175
  cm->add(c);
176
}
177

    
178
bool Scheduler::overlaps(const List &l, const Recording &r) const 
179
{
180
  for (List::const_iterator i = l.begin(); i != l.end(); i++)
181
    if (i->getStartTime() <= r.getStartTime() && 
182
        r.getStartTime() <= i->getStopTime())
183
      return true;
184
  return false;
185
}
186

    
187
void Scheduler::checkOverlappingBlock(const List &l, StringList &interruptions) const
188
{
189
  FakeTunerManager fakeTM(*tm);
190
  int devNull = open("/dev/null", O_WRONLY);
191
  
192
  typedef std::list<Connection *> ConnectionList;
193
  typedef std::map<Connection *, Recording> ConnectionMap;
194

    
195
  ConnectionList connections;
196
  ConnectionMap programs;
197

    
198
  // Here would be the place to set up any existing connections
199
  // We assume that the devices are unoccupied. This might
200
  // not be true, if for example the scheduled program is
201
  // due soon and a channel is currently connected
202
  // at high priority.
203

    
204
  for (List::const_iterator i = l.begin(); i != l.end(); i++) {
205
    const Recording &r = *i;
206

    
207
    int now = r.getStartTime();
208
    
209
    // Expire old connections
210
    for (ConnectionList::iterator j = connections.begin(); j != connections.end(); ) {
211
      Connection *e = *j;
212

    
213
      if (now > e->getStopTime()) {
214
        delete e;
215
        j = connections.erase(j);
216
      }
217
      else j++;
218
    }
219
      
220
    // Add in a new connection
221
    Connection *c = new Connection( &fakeTM, dup(devNull) );
222
    programs[c] = r;
223
    c->setStopTime( r.getStopTime() );
224

    
225
    if (!c->tune(r.getType(), r.getChannel(), r.getConvertToPS(), r.getPriority())) {
226
      interruptions.push_back( "Failed to start recording: " + r.toString() );
227
      delete c;
228
    }
229
    else {
230
      // Check to see if the new connection displaced any of the
231
      // others
232

    
233
      for (ConnectionList::iterator j = connections.begin(); j != connections.end(); ) {
234
        Connection *e = *j;
235
        
236
        if (e->getInterrupted()) {
237
          interruptions.push_back( "Recording " + r.toString() + " interrupts " + programs[e].toString() );
238
          delete e;
239
          j = connections.erase(j);
240
        }
241
        else j++;
242
      }
243

    
244
      // Add the new connection to the list
245
      connections.push_back(c);
246
    }
247
  }
248

    
249
  // Terminate the last connections
250

    
251
  for (ConnectionList::iterator j = connections.begin(); j != connections.end(); j++)
252
    delete *j;
253

    
254
  ::close(devNull);
255
}
256

    
257
void Scheduler::checkOverlaps(StringList &interruptions) const
258
{
259
  /** Find overlapping schedules and check that the it is possible
260
      to schedule them */
261

    
262
  for (List::const_iterator i = recordings.begin(); i != recordings.end(); ) {
263
    const Recording &cur = *i;
264

    
265
    i++;
266
    if (i == recordings.end())
267
      break;
268

    
269
    List overlapping;
270

    
271
    overlapping.push_back(cur);
272
    while (i != recordings.end() && overlaps(overlapping, *i)) {
273
      overlapping.push_back(*i);
274
      i++;
275
    }
276

    
277
    if (overlapping.size() > 1) {
278
      checkOverlappingBlock(overlapping, interruptions);
279
    }
280
  }
281
}
282

    
283
void Scheduler::getSchedule(StringList &s) const
284
{
285
  for (List::const_iterator i = recordings.begin(); i != recordings.end(); i++)
286
    s.push_back((*i).toString());
287
}
288

    
289
bool Scheduler::remove(int job)
290
{
291
  for (List::iterator i = recordings.begin(); i != recordings.end(); ) {
292
    if ((*i).getJob() == job) {
293
      recordings.erase(i);
294
      return true;
295
    }
296
    else i++;
297
  }
298
  return false;
299
}
300

    
301
bool Scheduler::getNextJobTime( time_t &time ) const
302
{
303
  if (recordings.empty())
304
    return false;
305

    
306
  const Recording &r = *recordings.begin();
307
  time = r.getStartTime();
308
  return true;
309
}