<%pre> #include <livefeatures.h> #include <setup.h> #include <tools.h> #include <timers.h> #include <epg_events.h> using namespace std; using namespace vdrlive; struct SchedEntry { string title; string short_description; string description; string description_trunc; string start; string end; string day; string epgid; bool truncated; bool has_timer; int start_row; int row_count; }; std::string channel_groups_setting; std::vector<std::string> channel_groups_names; std::vector< std::vector<int> > channel_groups_numbers; std::vector<std::string> times_names; std::vector<time_t> times_start; </%pre> <%args> int channel = -1; unsigned int time_para = 0; </%args> <%session scope="global"> bool logged_in(false); </%session> <%request scope="page"> unsigned int channel_group=0; unsigned int time_selected=0; </%request> <%include>page_init.eh</%include> <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); pageTitle = tr("MultiSchedule"); #if VDRVERSNUM < 20301 ReadLock channelsLock( Channels ); if ( !channelsLock ) throw HtmlError( tr("Couldn't aquire access to channels, please try again later.") ); #endif #define MAX_CHANNELS 10 #define MAX_DAYS 3 #define MAX_HOURS 8 #define MINUTES_PER_ROW 5 #define CHARACTERS_PER_ROW 30 if ( ( channel_groups_setting.compare(LiveSetup().GetChannelGroups()) != 0 ) || ( channel_groups_numbers.size() == 0 ) ) { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; #endif // build the groups of channels to display std::string channelGroups=LiveSetup().GetChannelGroups(); if ( channelGroups.empty() ) { // setup default channel groups int lastChannel = LiveSetup().GetLastChannel(); if ( lastChannel == 0 ) #if VDRVERSNUM >= 20301 lastChannel = Channels->MaxNumber(); #else lastChannel = Channels.MaxNumber(); #endif std::stringstream groups; int i = 0; #if VDRVERSNUM >= 20301 for (cChannel *channel = (cChannel *)Channels->First(); channel && (channel->Number() <= lastChannel); channel = (cChannel *)Channels->Next(channel)) #else for (cChannel *channel = Channels.First(); channel && (channel->Number() <= lastChannel); channel = Channels.Next(channel)) #endif { if (channel->GroupSep()) continue; groups << channel->Number(); if ( (++i % 5) == 0 ) groups << ";"; else groups << ","; } channelGroups = groups.str(); LiveSetup().SetChannelGroups( channelGroups ); } channel_groups_names.clear(); channel_groups_numbers.clear(); channel_groups_setting = channelGroups; size_t groupSep; std::string thisGroup = ""; while ( ! channelGroups.empty() ) { groupSep = channelGroups.find(';'); thisGroup = channelGroups.substr(0, groupSep ); if ( groupSep != channelGroups.npos ) channelGroups.erase(0, groupSep+1 ); else channelGroups=""; int cur_group_count=0; channel_groups_names.push_back( std::string() ); channel_groups_numbers.push_back( std::vector<int>() ); while ( !thisGroup.empty() ) { std::string thisChannel; try { if ( cur_group_count != 0 ) channel_groups_names.back() += std::string( " - " ); size_t channelSep = thisGroup.find(','); thisChannel = thisGroup.substr(0, channelSep ); if ( channelSep != thisGroup.npos ) thisGroup.erase( 0, channelSep+1 ); else thisGroup = ""; int channel_no = lexical_cast< int > (thisChannel); #if VDRVERSNUM >= 20301 cChannel* Channel = (cChannel *)Channels->GetByNumber( channel_no ); #else cChannel* Channel = Channels.GetByNumber( channel_no ); #endif if ( !Channel ) { esyslog("Live: could not find channel no '%s'.", thisChannel.c_str() ); continue; } channel_groups_names.back() += std::string( Channel->Name() ); channel_groups_numbers.back().push_back( Channel->Number() ); cur_group_count++; if ( cur_group_count>=MAX_CHANNELS ) { // start new group if group gets too large cur_group_count=0; channel_groups_names.push_back( std::string() ); channel_groups_numbers.push_back( std::vector<int>() ); } } catch ( const bad_lexical_cast & ) { esyslog("Live: could not convert '%s' into a channel number", thisChannel.c_str()); continue; } } } } if ( channel < 0 ) { if (cDevice::CurrentChannel()) { // find group corresponding to current channel int curGroup =0; int curChannel = cDevice::CurrentChannel(); for ( std::vector< std::vector<int> >::iterator grIt = channel_groups_numbers.begin(); grIt != channel_groups_numbers.end() && channel < 0; ++grIt, ++curGroup ) { for ( std::vector<int>::iterator chIt = (*grIt).begin(); chIt != (*grIt).end() && channel < 0; ++ chIt ) { if ( *chIt == curChannel ) channel_group = channel = curGroup; } } // if nothing is found, fall back to group 0 if ( channel < 0 ) channel = 0; } else { channel_group = channel; } } if ( channel >= (int)channel_groups_numbers.size() ) channel = 0; channel_group = channel; { // build time list times_names.clear(); times_start.clear(); // calculate time of midnight (localtime) and convert back to GMT time_t now = (time(NULL)/3600)*3600; time_t now_local = time(NULL); struct tm tm_r; if ( localtime_r( &now_local, &tm_r ) == 0 ) { ostringstream builder; builder << "cannot represent timestamp " << now_local << " as local time"; throw runtime_error( builder.str() ); } tm_r.tm_hour=0; tm_r.tm_min=0; tm_r.tm_sec=0; time_t midnight = mktime( &tm_r ); // add four 8h steps per day to the time list for (int i=0; i<4*MAX_DAYS ; i++ ) { times_start.push_back( midnight + MAX_HOURS*3600*i ); } vector< string > parts = StringSplit( LiveSetup().GetTimes(), ';' ); vector< time_t > offsets; vector< string >::const_iterator part = parts.begin(); for ( ; part != parts.end(); ++part ) { try { unsigned int sep = (*part).find(':'); std::string hour = (*part).substr(0, sep ); if ( sep == (*part).npos ) { esyslog("Live: Error parsing time '%s'", (*part).c_str() ); continue; } std::string min = (*part).substr(sep+1, (*part).npos ); offsets.push_back( lexical_cast<time_t>( hour )*60*60 + lexical_cast<time_t>( min ) *60 ); } catch ( const bad_lexical_cast & ) { esyslog("Live: Error parsing time '%s'", part->c_str() ); }; }; // add the time of the favourites to the time list for (int i=0; i< MAX_DAYS ; i++ ) { vector< time_t >::const_iterator offset = offsets.begin(); for ( ; offset != offsets.end(); ++offset ) { times_start.push_back( midnight + 24*3600*i + *offset ); } } // add now times_start.push_back( now ); // sort the times std::sort( times_start.begin(), times_start.end() ); // delete every time which has already passed while ( *times_start.begin()< now ) times_start.erase(times_start.begin() ); // build the corresponding names for ( vector< time_t >::const_iterator start = times_start.begin(); start != times_start.end(); ++start ) { times_names.push_back(FormatDateTime( tr("%A, %x"), *start) +std::string(" ")+ FormatDateTime( tr("%I:%M %p"), *start) ); } // the first time is now times_names[0]=tr("Now"); if ( time_para >= times_names.size() ) time_para = times_names.size()-1; time_selected=time_para; } </%cpp> <& pageelems.doc_type &> <html> <head> <title>VDR Live - <$ pageTitle $></title> <& pageelems.stylesheets &> <& pageelems.ajax_js &> </head> <body> <& pageelems.logo &> <& menu active=("multischedule") component=("multischedule.channel_selection") &> <div class="inhalt"> <%cpp> #if VDRVERSNUM < 20301 cSchedulesLock schedulesLock; cSchedules const* schedules = cSchedules::Schedules( schedulesLock ); #endif time_t now = time(NULL); if ( time_para >= times_start.size() ) time_para = times_start.size()-1; time_t sched_start = (times_start[ time_para ]/300)*300; time_t max_hours; try { max_hours = lexical_cast<time_t>( LiveSetup().GetScheduleDuration() ); } catch ( const bad_lexical_cast & ) { esyslog("Live: could not convert '%s' into a schedule duration", LiveSetup().GetScheduleDuration().c_str()); max_hours = 8; }; if (max_hours > 48) max_hours = 48; time_t sched_end = sched_start + 60 * 60 * max_hours; int sched_end_row = ( sched_end - sched_start ) / 60 / MINUTES_PER_ROW; std::list<SchedEntry> table[MAX_CHANNELS]; std::vector<std::string> channel_names(channel_groups_numbers[ channel ].size() ); std::vector<tChannelID> channel_IDs(channel_groups_numbers[ channel ].size() ); if ( channel >= (int)channel_groups_numbers.size() ) channel = channel_groups_numbers.size()-1; //for ( int chan = 0; chan<MAX_CHANNELS; chan++) for ( unsigned int j = 0; j<channel_groups_numbers[ channel ].size(); j++) { int prev_row = -1; int chan = channel_groups_numbers[ channel ][ j ]; cChannel* Channel; #if VDRVERSNUM >= 20301 { LOCK_CHANNELS_READ; Channel = (cChannel *)Channels->GetByNumber( chan ); } #else Channel = Channels.GetByNumber( chan ); #endif if ( ! Channel ) continue; if ( Channel->GroupSep() || !Channel->Name() || !*Channel->Name() ) continue; channel_names[ j ] = Channel->Name(); channel_IDs[ j ] = Channel->GetChannelID(); cSchedule const* Schedule; #if VDRVERSNUM >= 20301 { LOCK_SCHEDULES_READ; Schedule = Schedules->GetSchedule( Channel ); } #else Schedule = schedules->GetSchedule( Channel ); #endif if ( ! Schedule ) continue; for (const cEvent *Event = Schedule->Events()->First(); Event; Event = Schedule->Events()->Next(Event) ) { if (Event->EndTime() <= sched_start ) continue; if (Event->StartTime() >= sched_end ) continue; EpgInfoPtr epgEvent = EpgEvents::CreateEpgInfo(Channel, Event); if ( prev_row < 0 && Event->StartTime() > sched_start + MINUTES_PER_ROW ) { // insert dummy event at start table[ j ].push_back( SchedEntry() ); SchedEntry &en=table[ j ].back(); int event_start_row = (Event->StartTime() - sched_start) / 60 / MINUTES_PER_ROW; en.start_row = 0; en.row_count = event_start_row; // no title and no start time = dummy event en.title = ""; en.start = ""; prev_row = en.start_row + en.row_count; } table[ j ].push_back( SchedEntry() ); SchedEntry &en=table[j].back(); en.title = epgEvent->Title(); en.short_description = epgEvent->ShortDescr(); en.description = epgEvent->LongDescr(); en.start = epgEvent->StartTime(tr("%I:%M %p")); en.end = epgEvent->EndTime(tr("%I:%M %p")); en.day = epgEvent->StartTime(tr("%A, %b %d %Y")); en.epgid = EpgEvents::EncodeDomId(Channel->GetChannelID(), Event->EventID()); en.has_timer = LiveTimerManager().GetTimer(Event->EventID(), Channel->GetChannelID() ) != 0; en.start_row = prev_row > 0 ? prev_row : 0; int end_time = Schedule->Events()->Next(Event) ? Schedule->Events()->Next(Event)->StartTime() : Event->EndTime(); if (end_time > sched_end) end_time = sched_end; int next_event_start_row = (end_time - sched_start) / 60 / MINUTES_PER_ROW; en.row_count = next_event_start_row - en.start_row; if ( en.row_count < 1 ) en.row_count = 1; prev_row = en.start_row + en.row_count; // truncate description if too long en.truncated=false; en.description_trunc=StringWordTruncate( en.description, CHARACTERS_PER_ROW*(en.row_count-2), en.truncated ); }; if ( table[ j ].begin() == table[ j ].end() ) { // no entries... create a single dummy entry table[ j ].push_back( SchedEntry() ); SchedEntry &en=table[ j ].back(); en.start_row = 0; en.row_count = sched_end_row; // no title and no start time = dummy event en.title = ""; en.start = ""; } } </%cpp> <table class="mschedule" cellspacing="0" cellpadding="0"> <%cpp> </%cpp> <tr class=" topaligned "> <td > <div class="boxheader"> <div><div><$ tr("Time") $></div></div> </div></td> <td class="time spacer"> </td> <%cpp> for ( unsigned int channel = 0; channel< channel_names.size() ; channel++) { </%cpp> <td> <div class="boxheader"> <div> <div><$ channel_names[channel] $> <# reply.sout() automatically escapes special characters to html entities #> <& pageelems.ajax_action_href action="switch_channel" tip=(tr("Switch to this channel.")) param=(channel_IDs[channel]) image="zap.png" alt="" &> <& pageelems.vlc_stream_channel channelId=(channel_IDs[channel]) &> </div></div> </div></td> <td class="time spacer"> </td> <%cpp> } </%cpp> </tr> <%cpp> bool odd=true; std::list<SchedEntry>::iterator cur_event[ MAX_CHANNELS ]; for (int i=0;i<MAX_CHANNELS;i++) cur_event[i]=table[i].begin(); for (int row = 0 ; row < sched_end_row; row++ ) { int minutes= ( (sched_start + row * 60 * MINUTES_PER_ROW ) % 3600 ) / 60; string row_class; if ( minutes < MINUTES_PER_ROW ) { // full hour, swap odd/even odd = !odd; }; if ( (sched_start + row * 60 * MINUTES_PER_ROW ) <= now && (sched_start + (row+1) * 60 * MINUTES_PER_ROW ) > now ) { row_class +=" current_row "; } row_class += odd ? " odd " : " even "; </%cpp> <tr> <td class=" time leftcol rightcol <$ row_class $>"> <%cpp> if ( minutes < MINUTES_PER_ROW ) { </%cpp> <$ FormatDateTime( tr("%I:%M %p"), sched_start + row * 60 * MINUTES_PER_ROW ) $> <%cpp> } else { </%cpp> <%cpp> } </%cpp> </td> <%cpp> for ( unsigned int channel = 0; channel< channel_names.size() ; channel++) { // output spacer column </%cpp> <td class = " time spacer " > </td> <%cpp> if ( cur_event[channel] == table[channel].end() || cur_event[channel]->start_row != row ) // no new event in this channel, skip it continue; SchedEntry &en=*cur_event[channel]; if (en.title.empty() && en.start.empty() ) { // empty dummy event </%cpp> <td class="event topaligned leftcol rightcol" rowspan="<$ en.row_count $>"> </td> <%cpp> ++cur_event[channel]; continue; } // output an event cell </%cpp> <td class="event topaligned leftcol rightcol <$ en.has_timer ? "has_timer" : "" $>" rowspan="<$ en.row_count $>"> <div class=" content1 " > <div class=" tools1 " > <& pageelems.event_timer epgid=(en.epgid) &> <%cpp> if (LiveFeatures<features::epgsearch>().Recent() ) { </%cpp> <a href="searchresults.html?searchplain=<$ StringUrlEncode(en.title) $>"><img src="<$ LiveSetup().GetThemedLink("img", "search.png") $>" alt="" <& tooltip.hint text=(tr("Search for repeats.")) &>></img></a> <%cpp> } else { </%cpp><img src="img/transparent.png" width="16" height="16"><%cpp> } </%cpp> <& pageelems.imdb_info_href title=(en.title) &> </div><div class= "start withmargin"><$ en.start $></div> <div class="title withmargin"><a <& tooltip.hint text=(StringEscapeAndBreak(tr("Click to view details."))) &><& tooltip.display domId=en.epgid &>><$ en.title $></a></div> <%cpp> if ( en.row_count>2 && !en.short_description.empty() ) { </%cpp> <div class="short withmargin"><$ en.short_description.empty() ? " " : en.short_description $></div> <%cpp> } if ( en.row_count>3 && ! en.description_trunc.empty() ) { </%cpp> <div class="description withmargin"><$en.description_trunc$>... <%cpp> if ( en.truncated ) { </%cpp> <a <& tooltip.hint text=(StringEscapeAndBreak(tr("Click to view details."))) &><& tooltip.display domId=en.epgid &>> <$ tr("more") $></a> <%cpp> } </%cpp> </div> <%cpp> } </%cpp> </div></div> </td> <%cpp> // move to next event for this channel ++cur_event[channel]; } </%cpp> </tr> <%cpp> } </%cpp> <tr> <%cpp> for ( unsigned int channel = 0; channel <= channel_names.size() ; channel++) { </%cpp> <td class = " event leftcol rightcol bottomrow " > </td> <td class = " time spacer " > </td> <%cpp> } </%cpp> </tr> </table> </div> </body> </html> <%include>page_exit.eh</%include> <%def channel_selection> <form action="multischedule.html" method="get" id="channels"> <span> <label for="channel"><$ tr("Channel") $>: <span class="bold"></span></label> <select name="channel" id="channel" onchange="document.forms.channels.submit()" > % for ( unsigned int i = 0; i < channel_groups_names.size(); ++i ) { % <option value="<$ i $>" % if ( i == channel_group ) % { selected="selected" % } ><$ channel_groups_names[i] $></option> % } </select> <label for="time_para"><$ tr("Time") $>: <span class="bold"></span></label> <select name="time_para" id="time_para" onchange="document.forms.channels.submit()" > % for ( unsigned int i = 0; i < times_names.size(); ++i ) { % <option value="<$ i $>" % if ( i == time_selected ) % { selected="selected" % } ><$ times_names[i] $></option> % } </select> % // <& pageelems.ajax_action_href action="switch_channel" tip=(tr("Switch to this channel.")) param=(Channel->GetChannelID()) image="zap.png" alt="" &> % // <& pageelems.vlc_stream_channel channelId=(Channel->GetChannelID()) &> </span> </form> </%def>