<%pre> #include #include #include #include #include #include #include "exception.h" #include "livefeatures.h" #include "setup.h" #include "tools.h" #include "timers.h" #include "epg_events.h" #include "i18n.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 channel_groups_names; std::vector< std::vector > channel_groups_numbers; std::vector times_names; std::vector times_start; <%args> int channel = -1; unsigned int time_para = 0; <%session scope="global"> bool logged_in(false); <%request scope="page"> unsigned int channel_group=0; unsigned int time_selected=0; <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); pageTitle = tr("MultiSchedule"); ReadLock channelsLock( Channels ); if ( !channelsLock ) throw HtmlError( tr("Couldn't aquire access to channels, please try again later.") ); #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 ) ) { // 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 ) lastChannel = Channels.MaxNumber(); std::stringstream groups; int i = 0; for (cChannel *channel = Channels.First(); channel && (channel->Number() <= lastChannel); channel = Channels.Next(channel)) { 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() ); 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); cChannel* Channel = Channels.GetByNumber( channel_no ); 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() ); } } 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 >::iterator grIt = channel_groups_numbers.begin(); grIt != channel_groups_numbers.end() && channel < 0; ++grIt, ++curGroup ) { for ( std::vector::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( hour )*60*60 + lexical_cast( 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; } <& pageelems.doc_type &> VDR Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("multischedule") component=("multischedule.channel_selection") &>
<%cpp> cSchedulesLock schedulesLock; cSchedules const* schedules = cSchedules::Schedules( schedulesLock ); 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( 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 table[MAX_CHANNELS]; std::vector channel_names(channel_groups_numbers[ channel ].size() ); std::vector channel_IDs(channel_groups_numbers[ channel ].size() ); if ( channel >= (int)channel_groups_numbers.size() ) channel = channel_groups_numbers.size()-1; //for ( int chan = 0; chanGroupSep() || Channel->Name() == '\0' ) continue; channel_names[ j ] = Channel->Name(); channel_IDs[ j ] = Channel->GetChannelID(); cSchedule const* Schedule = schedules->GetSchedule( Channel ); 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> <%cpp> for ( unsigned int channel = 0; channel< channel_names.size() ; channel++) { <%cpp> } <%cpp> bool odd=true; std::list::iterator cur_event[ MAX_CHANNELS ]; for (int i=0;i now ) { row_class +=" current_row "; } row_class += odd ? " odd " : " even "; <%cpp> for ( unsigned int channel = 0; channel< channel_names.size() ; channel++) { // output spacer column <%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> ++cur_event[channel]; continue; } // output an event cell <%cpp> // move to next event for this channel ++cur_event[channel]; } <%cpp> } <%cpp> for ( unsigned int channel = 0; channel <= channel_names.size() ; channel++) { <%cpp> }
<$ tr("Time") $>
 
<$ 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]) &>
 
<%cpp> if ( minutes < MINUTES_PER_ROW ) { <$ FormatDateTime( tr("%I:%M %p"), sched_start + row * 60 * MINUTES_PER_ROW ) $> <%cpp> } else {   <%cpp> }   " rowspan="<$ en.row_count $>">
<& pageelems.event_timer epgid=(en.epgid) &> <%cpp> if (LiveFeatures().Recent() ) { " alt="" <& tooltip.hint text=(tr("Search for repeats.")) &>> <%cpp> } else { <%cpp> } <& pageelems.imdb_info_href title=(en.title) &>
<$ en.start $>
<%cpp> if ( en.row_count>2 && !en.short_description.empty() ) {
<$ en.short_description.empty() ? " " : en.short_description $>
<%cpp> } if ( en.row_count>3 && ! en.description_trunc.empty() ) {
<$en.description_trunc$>... <%cpp> if ( en.truncated ) { <& tooltip.display domId=en.epgid &>> <$ tr("more") $> <%cpp> }
<%cpp> }
   
<%include>page_exit.eh <%def channel_selection>
% // <& 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()) &>