# include "selfdrive/ui/replay/route.h"
# include <QEventLoop>
# include <QJsonArray>
# include <QJsonDocument>
# include <QRegExp>
# include "selfdrive/hardware/hw.h"
# include "selfdrive/ui/qt/api.h"
# include "selfdrive/ui/replay/util.h"
Route : : Route ( const QString & route , const QString & data_dir ) : route_ ( parseRoute ( route ) ) , data_dir_ ( data_dir ) { }
RouteIdentifier Route : : parseRoute ( const QString & str ) {
QRegExp rx ( R " (^([a-z0-9]{16}) ( [ | _ / ] ) ( \ d { 4 } - \ d { 2 } - \ d { 2 } - - \ d { 2 } - \ d { 2 } - \ d { 2 } ) ( ? : ( - - | / ) ( \ d * ) ) ? $ ) " );
if ( rx . indexIn ( str ) = = - 1 ) return { } ;
const QStringList list = rx . capturedTexts ( ) ;
return { list [ 1 ] , list [ 3 ] , list [ 5 ] . toInt ( ) , list [ 1 ] + " | " + list [ 3 ] } ;
}
bool Route : : load ( ) {
if ( route_ . str . isEmpty ( ) ) {
qInfo ( ) < < " invalid route format " ;
return false ;
}
return data_dir_ . isEmpty ( ) ? loadFromServer ( ) : loadFromLocal ( ) ;
}
bool Route : : loadFromServer ( ) {
QEventLoop loop ;
HttpRequest http ( nullptr , ! Hardware : : PC ( ) ) ;
QObject : : connect ( & http , & HttpRequest : : failedResponse , [ & ] { loop . exit ( 0 ) ; } ) ;
QObject : : connect ( & http , & HttpRequest : : timeoutResponse , [ & ] { loop . exit ( 0 ) ; } ) ;
QObject : : connect ( & http , & HttpRequest : : receivedResponse , [ & ] ( const QString & json ) {
loop . exit ( loadFromJson ( json ) ) ;
} ) ;
http . sendRequest ( " https://api.commadotai.com/v1/route/ " + route_ . str + " /files " ) ;
return loop . exec ( ) ;
}
bool Route : : loadFromJson ( const QString & json ) {
QRegExp rx ( R " ( \ /( \ d+) \ / ) " ) ;
for ( const auto & value : QJsonDocument : : fromJson ( json . trimmed ( ) . toUtf8 ( ) ) . object ( ) ) {
for ( const auto & url : value . toArray ( ) ) {
QString url_str = url . toString ( ) ;
if ( rx . indexIn ( url_str ) ! = - 1 ) {
addFileToSegment ( rx . cap ( 1 ) . toInt ( ) , url_str ) ;
}
}
}
return ! segments_ . empty ( ) ;
}
bool Route : : loadFromLocal ( ) {
QDir log_dir ( data_dir_ ) ;
for ( const auto & folder : log_dir . entryList ( QDir : : Dirs | QDir : : NoDot | QDir : : NoDotDot , QDir : : NoSort ) ) {
int pos = folder . lastIndexOf ( " -- " ) ;
if ( pos ! = - 1 & & folder . left ( pos ) = = route_ . timestamp ) {
const int seg_num = folder . mid ( pos + 2 ) . toInt ( ) ;
QDir segment_dir ( log_dir . filePath ( folder ) ) ;
for ( const auto & f : segment_dir . entryList ( QDir : : Files ) ) {
addFileToSegment ( seg_num , segment_dir . absoluteFilePath ( f ) ) ;
}
}
}
return ! segments_ . empty ( ) ;
}
void Route : : addFileToSegment ( int n , const QString & file ) {
const QString name = QUrl ( file ) . fileName ( ) ;
if ( name = = " rlog.bz2 " ) {
segments_ [ n ] . rlog = file ;
} else if ( name = = " qlog.bz2 " ) {
segments_ [ n ] . qlog = file ;
} else if ( name = = " fcamera.hevc " ) {
segments_ [ n ] . road_cam = file ;
} else if ( name = = " dcamera.hevc " ) {
segments_ [ n ] . driver_cam = file ;
} else if ( name = = " ecamera.hevc " ) {
segments_ [ n ] . wide_road_cam = file ;
} else if ( name = = " qcamera.ts " ) {
segments_ [ n ] . qcamera = file ;
}
}
// class Segment
Segment : : Segment ( int n , const SegmentFile & files , bool load_dcam , bool load_ecam ) : seg_num ( n ) {
static std : : once_flag once_flag ;
std : : call_once ( once_flag , [ = ] ( ) { if ( ! CACHE_DIR . exists ( ) ) QDir ( ) . mkdir ( CACHE_DIR . absolutePath ( ) ) ; } ) ;
// [RoadCam, DriverCam, WideRoadCam, log]. fallback to qcamera/qlog
const QString file_list [ ] = {
files . road_cam . isEmpty ( ) ? files . qcamera : files . road_cam ,
load_dcam ? files . driver_cam : " " ,
load_ecam ? files . wide_road_cam : " " ,
files . rlog . isEmpty ( ) ? files . qlog : files . rlog ,
} ;
for ( int i = 0 ; i < std : : size ( file_list ) ; i + + ) {
if ( ! file_list [ i ] . isEmpty ( ) ) {
loading_ + + ;
QThread * t = new QThread ( ) ;
QObject : : connect ( t , & QThread : : started , [ = ] ( ) { loadFile ( i , file_list [ i ] . toStdString ( ) ) ; } ) ;
loading_threads_ . emplace_back ( t ) - > start ( ) ;
}
}
}
Segment : : ~ Segment ( ) {
aborting_ = true ;
for ( QThread * t : loading_threads_ ) {
if ( t - > isRunning ( ) ) {
t - > quit ( ) ;
t - > wait ( ) ;
}
delete t ;
}
}
void Segment : : loadFile ( int id , const std : : string file ) {
const bool is_remote = file . find ( " https:// " ) = = 0 ;
const std : : string local_file = is_remote ? cacheFilePath ( file ) : file ;
bool file_ready = util : : file_exists ( local_file ) ;
if ( ! file_ready & & is_remote ) {
file_ready = downloadFile ( id , file , local_file ) ;
}
if ( ! aborting_ & & file_ready ) {
if ( id < MAX_CAMERAS ) {
frames [ id ] = std : : make_unique < FrameReader > ( ) ;
success_ = success_ & & frames [ id ] - > load ( local_file ) ;
} else {
std : : string decompressed = cacheFilePath ( local_file + " .decompressed " ) ;
if ( ! util : : file_exists ( decompressed ) ) {
std : : ofstream ostrm ( decompressed , std : : ios : : binary ) ;
readBZ2File ( local_file , ostrm ) ;
}
log = std : : make_unique < LogReader > ( ) ;
success_ = success_ & & log - > load ( decompressed ) ;
}
}
if ( ! aborting_ & & - - loading_ = = 0 ) {
emit loadFinished ( success_ ) ;
}
}
bool Segment : : downloadFile ( int id , const std : : string & url , const std : : string local_file ) {
bool ret = false ;
int retries = 0 ;
while ( ! aborting_ ) {
ret = httpMultiPartDownload ( url , local_file , id < MAX_CAMERAS ? 3 : 1 , & aborting_ ) ;
if ( ret | | aborting_ ) break ;
if ( + + retries > max_retries_ ) {
qInfo ( ) < < " download failed after retries " < < max_retries_ ;
break ;
}
qInfo ( ) < < " download failed, retrying " < < retries ;
}
return ret ;
}
std : : string Segment : : cacheFilePath ( const std : : string & file ) {
QString url_no_query = QUrl ( file . c_str ( ) ) . toString ( QUrl : : RemoveQuery ) ;
QString sha256 = QCryptographicHash : : hash ( url_no_query . toUtf8 ( ) , QCryptographicHash : : Sha256 ) . toHex ( ) ;
return CACHE_DIR . filePath ( sha256 + " . " + QFileInfo ( url_no_query ) . suffix ( ) ) . toStdString ( ) ;
}