OSDN Git Service

ee258b5849b94fd31697f37a645247b099b8b5dc
[iptd/iPTd.git] / src / ry0 / iPTd / Reservation.cpp
1 /**\r
2  * @file Reservation.cpp\r
3  *\r
4  */\r
5 \r
6 #include <time.h>\r
7 #include <fcntl.h>\r
8 #include <io.h>\r
9 #include <direct.h>\r
10 \r
11 \r
12 #define DBG_LEVEL 3\r
13 #include "Raym/Log.h"\r
14 \r
15 #include "keys.h"\r
16 #include "ry0/iPTd/Reservation.h"\r
17 #include "ry0/iPTd/Controller.h"\r
18 \r
19 using namespace Raym;\r
20 \r
21 namespace ry0\r
22 {\r
23 namespace iPTd\r
24 {\r
25 \r
26 static const time_t OFFSET_OF_START_TIME        = -2;       // 録画開始時刻の補正(秒単位)\r
27 static const time_t OFFSET_OF_END_TIME          = -3;       // 録画停止時刻の補正(秒単位)\r
28 static const time_t OFFSET_OF_WAKEUP            = -240;     // 起動スケジュールの補正(秒単位)  注:休止するまでの時間(DEF_SUSPEND_TIME)よりも短くすること\r
29 static const time_t OFFSET_OF_SUPPRESSION_TIME  = -600;     // 録画開始前に休止の抑制を開始する時間(秒単位)\r
30 \r
31 Reservation::Reservation()\r
32 {\r
33     _controller         = NULL;\r
34     _tuner_count        = -1;\r
35     _tuners             = NULL;\r
36     _reservations_path  = NULL;\r
37     _reservations       = NULL;\r
38     _reservation_seq_id = -1;\r
39 }\r
40 \r
41 Reservation::~Reservation()\r
42 {\r
43     RELEASE(_reservations_path);\r
44     RELEASE(_reservations);\r
45 }\r
46 \r
47 Reservation *Reservation::alloc()\r
48 {\r
49     return new Reservation();\r
50 }\r
51 \r
52 Reservation *Reservation::initWithController(Controller *controller)\r
53 {\r
54     _controller  = controller;\r
55     _tuner_count = controller->_tunerCount;\r
56     _tuners      = controller->_tuners;\r
57 \r
58     _reservations_path = String::alloc()->initWithFormat("%s%s.iptd.reservations.plist", _controller->_system_path->cString(), Controller::_plist_prefix);\r
59     if (_reservations_path == NULL)\r
60     {\r
61         release();\r
62         return NULL;\r
63     }\r
64 \r
65     _reservations = Dictionary::alloc()->initWithContentsOfFile(_reservations_path);\r
66     if (_reservations == NULL)\r
67     {\r
68         DebugLog1("because \"%s\" is not exists, created.", _reservations_path->cString());\r
69         _reservations = Dictionary::alloc()->initWithCapacity(0);\r
70         if (_reservations == NULL)\r
71         {\r
72             release();\r
73             return NULL;\r
74         }\r
75         _reservation_seq_id = 1;\r
76     }\r
77     else\r
78     {\r
79         DebugLog1("reservations file: \"%s\"", _reservations_path->cString());\r
80 \r
81         _reservation_seq_id = _reservations->integerForKey(KEY_EPG_LAST_RESV_ID);\r
82     }\r
83 \r
84     _timer_periodic = Timer::alloc()->initWithTimeInterval(1.0, this, NULL, true);\r
85     if (_timer_periodic == NULL)\r
86     {\r
87         release();\r
88         return NULL;\r
89     }\r
90 \r
91     _timer_periodic->fire();\r
92 \r
93     return this;\r
94 }\r
95 \r
96 bool Reservation::canTerminate()\r
97 {\r
98     bool result = true;\r
99 \r
100     // 現在時刻取得\r
101     time_t now = time(NULL);\r
102 \r
103     for (int tuner = 0; tuner < _tuner_count; ++tuner)\r
104     {\r
105         Array *array = _reservations->arrayForKey(_tuners[tuner]->name());\r
106         if ((array == NULL) || (array->count() == 0))\r
107         {\r
108             // next tuner\r
109             continue;\r
110         }\r
111 \r
112         //\r
113         Dictionary *epg = (Dictionary *)array->objectAtIndex(0);\r
114         time_t start;\r
115         time_t end;\r
116         Controller::getTimeWithEPG(epg, &start, &end);\r
117 \r
118         if (start + OFFSET_OF_SUPPRESSION_TIME <= now)\r
119         {\r
120             // サスペンド不可\r
121             result = false;\r
122             break;\r
123         }\r
124     }\r
125 \r
126     return true;\r
127 }\r
128 \r
129 //\r
130 // 録画予約:チューナ/EPG指定\r
131 //\r
132 bool Reservation::reserve(int tuner, Dictionary *in_epg)\r
133 {\r
134     DebugLog2("Reservation::reserve(tuner, epg)");\r
135 \r
136     bool result = false;\r
137 \r
138     // lock\r
139     RaymLock(_controller);\r
140 \r
141     while ((0 <= tuner) && (tuner < _tuner_count) && (in_epg != NULL))\r
142     {\r
143         Dictionary *epg = Dictionary::dictionaryWithDictionary(in_epg);\r
144         if (epg == NULL)\r
145         {\r
146             DebugLog3("Dictionary::dictionaryWithDictionary() ng.");\r
147             break;\r
148         }\r
149 \r
150         Array *array = _reservations->arrayForKey(_tuners[tuner]->name());\r
151         if (array == NULL)\r
152         {\r
153             array = Array::arrayWithCapacity(0);\r
154             _reservations->setObject(array, _tuners[tuner]->name());\r
155         }\r
156 \r
157         time_t epg_start;\r
158         time_t epg_end;\r
159         Controller::getTimeWithEPG(epg, &epg_start, &epg_end);\r
160         DebugLog2("epg start: %ld, end: %ld\n", epg_start, epg_end);\r
161 \r
162         time_t pre_start = 0;\r
163         time_t pre_end = 0;\r
164         pre_end = time(NULL);\r
165         DebugLog2("pre_end: %ld", pre_end);\r
166 \r
167         for (uint i = 0; i < array->count(); ++i)\r
168         {\r
169             Dictionary *cur = (Dictionary *)array->objectAtIndex(i);\r
170             time_t cur_start;\r
171             time_t cur_end;\r
172             Controller::getTimeWithEPG(cur, &cur_start, &cur_end);\r
173             DebugLog2("cur start: %ld, end: %ld\n", cur_start, cur_end);\r
174             if ((pre_end <= epg_start) && (epg_end <= cur_start))\r
175             {\r
176                 DebugLog2("insert: %d\n", i);\r
177                 array->insertObject(epg, i);\r
178                 result = true;\r
179                 break;\r
180             }\r
181             pre_start = cur_start;\r
182             pre_end = cur_end;\r
183         }\r
184 \r
185         if (!result)\r
186         {\r
187             if (pre_end <= epg_start)\r
188             {\r
189                 DebugLog2("add\n");\r
190                 array->addObject(epg);\r
191                 result = true;\r
192             }\r
193             else\r
194             {\r
195                 DebugLog2("no add\n");\r
196             }\r
197         }\r
198         if (result)\r
199         {\r
200             epg->setInteger(_reservation_seq_id, KEY_EPG_RESV_ID);\r
201             _reservation_seq_id = (_reservation_seq_id + 1) % 1000000;\r
202             _reservations->setInteger(_reservation_seq_id, KEY_EPG_LAST_RESV_ID);\r
203 \r
204             //\r
205             _reservations->writeToFile(_reservations_path, true);\r
206         }\r
207 \r
208         break;\r
209     }\r
210 \r
211     // unlock\r
212     RaymUnlock(_controller);\r
213 \r
214     return result;\r
215 }\r
216 \r
217 //\r
218 // tuner: 0 - (_tunerCount - 1)  cancel current\r
219 // tuner: -1  cancel reserve_id\r
220 //\r
221 bool Reservation::cancel(int tuner, int reserve_id)\r
222 {\r
223     bool result = false;\r
224 \r
225     // lock\r
226     RaymLock(_controller);\r
227 \r
228     //\r
229     if ((0 <= tuner) && (tuner < _tuner_count))\r
230     {\r
231         Array *array = _reservations->arrayForKey(_tuners[tuner]->name());\r
232         if (array != NULL)\r
233         {\r
234             if (array->count() > 0)\r
235             {\r
236                 Dictionary *epg = (Dictionary *)array->objectAtIndex(0);\r
237                 String *status = epg->stringForKey(KEY_EPG_STATUS);\r
238                 if (status != NULL)\r
239                 {\r
240                     if (status->isEqualToString("running"))\r
241                     {\r
242                         epg->setString("stop", KEY_EPG_STATUS);\r
243                         result = true;\r
244                     }\r
245                 }\r
246             }\r
247         }\r
248     }\r
249 \r
250     //\r
251     else if ((tuner < 0) && (0 <= reserve_id) && (reserve_id < 1000000))\r
252     {\r
253         for (int i = 0; i < _tuner_count; ++i)\r
254         {\r
255             Array *array = _reservations->arrayForKey(_tuners[i]->name());\r
256             if (array != NULL)\r
257             {\r
258                 for (uint j = 0; j < array->count(); ++j)\r
259                 {\r
260                     Dictionary *epg = (Dictionary *)array->objectAtIndex(j);\r
261                     if (reserve_id == epg->integerForKey(KEY_EPG_RESV_ID))\r
262                     {\r
263                         String *status = epg->stringForKey(KEY_EPG_STATUS);\r
264                         if ((status != NULL) && status->isEqualToString("running"))\r
265                         {\r
266                             epg->setString("stop", KEY_EPG_STATUS);\r
267                         }\r
268                         else\r
269                         {\r
270                             array->removeObjectAtIndex(j);\r
271                         }\r
272                         result = true;\r
273                         break;\r
274                     }\r
275                 }\r
276             }\r
277             if (result)\r
278             {\r
279                 break;\r
280             }\r
281         }\r
282     }\r
283 \r
284     if (result)\r
285     {\r
286         _reservations->writeToFile(_reservations_path, true);\r
287     }\r
288 \r
289     // unlock\r
290     RaymUnlock(_controller);\r
291 \r
292     return result;\r
293 }\r
294 \r
295 std::string Reservation::createVideoPath(int tuner)\r
296 {\r
297     DebugLog2("Reservation::createVideoPath()");\r
298 \r
299     std::string result = "";\r
300 \r
301     while (true)\r
302     {\r
303         time_t now;\r
304         time(&now);\r
305         TM tm;\r
306         if (localtime_s(&tm, &now) != 0)\r
307         {\r
308             break;\r
309         }\r
310 \r
311         result = _controller->_props->stringForKey(KEY_STORE_PATH)->cString();\r
312         DebugLog2("result: %s\n", result.c_str());\r
313 \r
314         char tmp[128];\r
315         if (sprintf_s(tmp, sizeof(tmp), "\\%04d", tm.tm_year + 1900) < 0)\r
316         {\r
317             DebugLog0("sprintf_s() error: year\n");\r
318             result = "";\r
319             break;\r
320         }\r
321         result += tmp;\r
322         DebugLog2("result: %s\n", result.c_str());\r
323 \r
324         STAT stat;\r
325         if (_stat(result.c_str(), &stat) != 0)\r
326         {\r
327             if (_mkdir(result.c_str()) != 0)\r
328             {\r
329                 DebugLog0("_mkdir() error: year\n");\r
330                 result = "";\r
331                 break;\r
332             }\r
333             _stat(result.c_str(), &stat);\r
334         }\r
335         if ((stat.st_mode & _S_IFDIR) != _S_IFDIR)\r
336         {\r
337             DebugLog0("%s is not directory.\n", result.c_str());\r
338             result = "";\r
339             break;\r
340         }\r
341 \r
342         if (sprintf_s(tmp, sizeof(tmp), "\\%02d", tm.tm_mon + 1) < 0)\r
343         {\r
344             DebugLog0("sprintf_s() error: month\n");\r
345             result = "";\r
346             break;\r
347         }\r
348         result += tmp;\r
349         DebugLog2("result: %s\n", result.c_str());\r
350 \r
351         if (_stat(result.c_str(), &stat) != 0)\r
352         {\r
353             if (_mkdir(result.c_str()) != 0)\r
354             {\r
355                 DebugLog0("_mkdir() error: month\n");\r
356                 result = "";\r
357                 break;\r
358             }\r
359             _stat(result.c_str(), &stat);\r
360         }\r
361         if ((stat.st_mode & _S_IFDIR) != _S_IFDIR)\r
362         {\r
363             DebugLog0("%s is not directory.", result.c_str());\r
364             result = "";\r
365             break;\r
366         }\r
367 \r
368         if (sprintf_s(tmp, sizeof(tmp),\r
369                       "\\%04d%02d%02d_%02d%02d%02d_%03d_%s.ts",\r
370                       tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,\r
371                       _tuners[tuner]->channel(), _tuners[tuner]->name()) < 0)\r
372         {\r
373             DebugLog0("sprintf_s() error: filename");\r
374             result = "";\r
375             break;\r
376         }\r
377         result += tmp;\r
378         DebugLog2("result: %s\n", result.c_str());\r
379 \r
380         break;\r
381     }\r
382 \r
383     return result;\r
384 }\r
385 \r
386 void Reservation::timerExpired(Timer *timer, void *userInfo)\r
387 {\r
388     bool need_update = false;\r
389 \r
390 #ifdef RAYM_MEMORY_CHECK\r
391     DebugLog0("global_objc_count_ = %d", Raym::global_raym_count_);\r
392 #endif\r
393 \r
394     // lock\r
395     RaymLock(_controller);\r
396 \r
397     // 現在時刻取得\r
398     time_t now = time(NULL);\r
399 \r
400     DebugLog2("periodic: %d", now);\r
401 \r
402     //\r
403     for (int tuner = 0; tuner < _tuner_count; ++tuner)\r
404     {\r
405         Array *array = _reservations->arrayForKey(_tuners[tuner]->name());\r
406         if ((array == NULL) || (array->count() == 0))\r
407         {\r
408             // next tuner\r
409             continue;\r
410         }\r
411 \r
412         //\r
413         Dictionary *epg = (Dictionary *)array->objectAtIndex(0);\r
414         time_t start;\r
415         time_t end;\r
416         Controller::getTimeWithEPG(epg, &start, &end);\r
417         \r
418         //\r
419         // 録画停止要否チェック\r
420         //\r
421         bool stop_need = false;\r
422         while (true)\r
423         {\r
424             String *status = epg->stringForKey(KEY_EPG_STATUS);\r
425             if (status != NULL)\r
426             {\r
427                 if (status->isEqualToString("stop"))\r
428                 {\r
429                     stop_need = true;\r
430                     break;\r
431                 }\r
432                 if (!status->isEqualToString("running"))\r
433                 {\r
434                     break;\r
435                 }\r
436             }\r
437             if (end + OFFSET_OF_END_TIME <= now)\r
438             {\r
439                 stop_need = true;\r
440             }\r
441             break;\r
442         }\r
443         if (stop_need)\r
444         {\r
445             DebugLog2("I try stop\n");\r
446             int fd = _tuners[tuner]->stopRecording();\r
447             if (fd < 0)\r
448             {\r
449                 DebugLog1("stopRecording() error.\n");\r
450             }\r
451             else\r
452             {\r
453                 DebugLog2("stopRecording() ok\n");\r
454                 DebugLog0("stop recording of \"%s\"", _tuners[tuner]->name());\r
455                 _close(fd);\r
456             }\r
457             array->removeObject(epg);\r
458             \r
459             if (array->count() > 0)\r
460             {\r
461                 epg = (Dictionary *)array->objectAtIndex(0);\r
462             }\r
463             else\r
464             {\r
465                 epg = NULL;\r
466             }\r
467             need_update = true;\r
468         }\r
469 \r
470         if (epg == NULL)\r
471         {\r
472             // next tuner\r
473             continue;\r
474         }\r
475 \r
476         //\r
477         // 録画開始要否チェック\r
478         //\r
479         bool start_need = false;\r
480         start = end = 0;\r
481         Controller::getTimeWithEPG(epg, &start, &end);\r
482         if ((start != 0) && (end != 0))\r
483         {\r
484             String *status = epg->stringForKey(KEY_EPG_STATUS);\r
485             if ((status == NULL) || !(status->isEqualToString("running")))\r
486             {\r
487                 if (end + OFFSET_OF_END_TIME <= now)\r
488                 {\r
489                     // 既に終了時間が経過しているので削除する\r
490                     array->removeObject(epg);\r
491                 }\r
492                 else if (start + OFFSET_OF_START_TIME <= now)\r
493                 {\r
494                     start_need = true;\r
495                 }\r
496             }\r
497         }\r
498 \r
499         if (start_need)\r
500         {\r
501             DebugLog2("I need start.\n");\r
502             String *ch = epg->stringForKey(KEY_EPG_CHANNEL);\r
503             if (ch != NULL)\r
504             {\r
505                 int channel = atoi(ch->cString());\r
506                 DebugLog2("channel: %d\n", channel);\r
507                 std::string videopath = createVideoPath(tuner);\r
508                 if (videopath != "")\r
509                 {\r
510                     DebugLog2("videopath: %s\n", videopath.c_str());\r
511                     int fd = -1;\r
512                     if (_sopen_s(&fd, videopath.c_str(),\r
513                                  (_O_CREAT | _O_EXCL | _O_WRONLY | _O_BINARY | _O_TRUNC), _SH_DENYRW, (_S_IREAD | _S_IWRITE)) == 0)\r
514                     {\r
515                         DebugLog2("open ok.\n");\r
516                         bool startResult = true;\r
517                         if (_tuners[tuner]->channel() != channel)\r
518                         {\r
519                             if (!_controller->setChannel(tuner, channel))\r
520                             {\r
521                                 DebugLog3("setChannel() ng.");\r
522                                 startResult = false;\r
523                             }\r
524                         }\r
525 \r
526                         if (startResult)\r
527                         {\r
528                             if (_tuners[tuner]->startRecording(fd))\r
529                             {\r
530                                 DebugLog2("startRecording() ok.");\r
531                                 DebugLog0("start recording of \"%s\" to %s.", _tuners[tuner]->name(), videopath.c_str());\r
532                             }\r
533                             else\r
534                             {\r
535                                 DebugLog3("Tuner::startRecording() failed.");\r
536                                 startResult = false;\r
537                             }\r
538                         }\r
539 \r
540                         if (startResult)\r
541                         {\r
542                             epg->setString("running", KEY_EPG_STATUS);\r
543                         }\r
544                         else\r
545                         {\r
546                             _close(fd);\r
547                         }\r
548                     }\r
549                     else\r
550                     {\r
551                         DebugLog0("open ng. 0x%08x\n", errno);\r
552                     }\r
553                 }\r
554                 else\r
555                 {\r
556                     DebugLog0("Can't create videopath.\n");\r
557                 }\r
558             }\r
559             else\r
560             {\r
561                 DebugLog0("error.\n");\r
562             }\r
563         }\r
564     }\r
565 \r
566     if (need_update)\r
567     {\r
568         //\r
569         _reservations->writeToFile(_reservations_path, true);\r
570     }\r
571 \r
572 #if 0\r
573     // EPG収集時刻を取得\r
574     NSString *collect_str = _props->stringForKey(KEY_COLLECT_EPG_TIME);\r
575     if (collect_str != NULL)\r
576     {\r
577         // 秒に変換\r
578         time_t collect_time = 0;\r
579         getTimeWithString(collect_str, &collect_time);\r
580 \r
581         // 現在時刻と比較\r
582         if ((collect_time <= now) && (now < collect_time + 1))\r
583         {\r
584             // タイマが起動中か確認\r
585             if ((_timer_epg_s == NULL) || !_timer_epg_s->valid())\r
586             {\r
587                 // EPG収集用タイマ起動(ISDB-S)\r
588                 RELEASE(_timer_epg_s);\r
589                 _timer_epg_s = NSTimer::alloc()->initWithTimeInterval(DEF_COLLECT_EPG_DELAY, this, (void *)CMD_COLLECT_EPG_ISDB_S, true);\r
590                 if (_timer_epg_s != NULL)\r
591                 {\r
592                     _timer_epg_s->fire();\r
593                 }\r
594             }\r
595 \r
596             // タイマが起動中か確認\r
597             if ((_timer_epg_t == NULL) || !_timer_epg_t->valid())\r
598             {\r
599                 // EPG収集用タイマ起動(ISDB-T)\r
600                 RELEASE(_timer_epg_t);\r
601                 _timer_epg_t = NSTimer::alloc()->initWithTimeInterval(DEF_COLLECT_EPG_DELAY, this, (void *)CMD_COLLECT_EPG_ISDB_T, true);\r
602                 if (_timer_epg_t != NULL)\r
603                 {\r
604                     _timer_epg_t->fire();\r
605                 }\r
606             }\r
607         }\r
608     }\r
609 #endif\r
610 \r
611     // unlock\r
612     RaymUnlock(_controller);\r
613 \r
614     //\r
615     // 1/100秒単位が 0 に近くなるように次回T.O.を微調整\r
616     // ただし、windowsは精度が低いので期待しないことw\r
617     //\r
618 \r
619 #if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)\r
620     static const __time64_t DELTA_EPOCH_IN_MICROSECS = 11644473600000000Ui64;\r
621 #else\r
622     static const __time64_t DELTA_EPOCH_IN_MICROSECS = 11644473600000000ULL;\r
623 #endif\r
624     // 現在時刻を取得\r
625     FILETIME ft;\r
626     GetSystemTimeAsFileTime(&ft);\r
627 \r
628     // EPOCH秒への変換\r
629     __time64_t now_sec;\r
630     __time64_t now_usec;\r
631     now_sec = ft.dwHighDateTime;\r
632     now_sec <<= 32;\r
633     now_sec |= ft.dwLowDateTime;\r
634     now_sec /= 10;  /*convert into microseconds*/\r
635     now_sec -= DELTA_EPOCH_IN_MICROSECS;\r
636     now_usec = (now_sec % 1000000UL);\r
637     now_sec = now_sec / 1000000UL;\r
638 \r
639     TimeInterval interval = (TimeInterval)now_usec;\r
640     interval = interval / 1000000;\r
641     _timer_periodic->setTimeInterval(1.005 - interval);\r
642 \r
643 #ifdef RAYM_MEMORY_CHECK\r
644     DebugLog0("global_objc_count_ = %d", Raym::global_raym_count_);\r
645 #endif\r
646 }\r
647 \r
648 } // iPTd\r
649 } // ry0\r