# include "selfdrive/ui/replay/route.h"
# include <QEventLoop>
# include <QFile>
# include <QJsonArray>
# include <QJsonDocument>
# include <QRegExp>
# include <QThread>
# include <future>
# include "selfdrive/hardware/hw.h"
# include "selfdrive/ui/qt/api.h"
# include "selfdrive/ui/replay/util.h"
Route : : Route ( const QString & route ) : route_ ( route ) { }
bool Route : : load ( ) {
QEventLoop loop ;
auto onError = [ & loop ] ( const QString & err ) { loop . quit ( ) ; } ;
bool ret = false ;
HttpRequest http ( nullptr , ! Hardware : : PC ( ) ) ;
QObject : : connect ( & http , & HttpRequest : : failedResponse , onError ) ;
QObject : : connect ( & http , & HttpRequest : : timeoutResponse , onError ) ;
QObject : : connect ( & http , & HttpRequest : : receivedResponse , [ & ] ( const QString json ) {
ret = loadFromJson ( json ) ;
loop . quit ( ) ;
} ) ;
http . sendRequest ( " https://api.commadotai.com/v1/route/ " + route_ + " /files " ) ;
loop . exec ( ) ;
return ret ;
}
bool Route : : loadFromJson ( const QString & json ) {
QJsonObject route_files = QJsonDocument : : fromJson ( json . trimmed ( ) . toUtf8 ( ) ) . object ( ) ;
if ( route_files . empty ( ) ) {
qInfo ( ) < < " JSON Parse failed " ;
return false ;
}
QRegExp rx ( R " ( \ /( \ d+) \ / ) " ) ;
for ( const QString & key : route_files . keys ( ) ) {
for ( const auto & url : route_files [ key ] . toArray ( ) ) {
QString url_str = url . toString ( ) ;
if ( rx . indexIn ( url_str ) ! = - 1 ) {
const int seg_num = rx . cap ( 1 ) . toInt ( ) ;
if ( segments_ . size ( ) < = seg_num ) {
segments_ . resize ( seg_num + 1 ) ;
}
if ( key = = " logs " ) {
segments_ [ seg_num ] . rlog = url_str ;
} else if ( key = = " qlogs " ) {
segments_ [ seg_num ] . qlog = url_str ;
} else if ( key = = " cameras " ) {
segments_ [ seg_num ] . road_cam = url_str ;
} else if ( key = = " dcameras " ) {
segments_ [ seg_num ] . driver_cam = url_str ;
} else if ( key = = " ecameras " ) {
segments_ [ seg_num ] . wide_road_cam = url_str ;
} else if ( key = = " qcameras " ) {
segments_ [ seg_num ] . qcamera = url_str ;
}
}
}
}
return true ;
}
// class Segment
Segment : : Segment ( int n , const SegmentFile & segment_files , bool load_dcam , bool load_ecam ) : seg_num_ ( n ) , files_ ( segment_files ) {
static std : : once_flag once_flag ;
std : : call_once ( once_flag , [ = ] ( ) {
if ( ! CACHE_DIR . exists ( ) ) QDir ( ) . mkdir ( CACHE_DIR . absolutePath ( ) ) ;
} ) ;
// fallback to qcamera/qlog
road_cam_path_ = files_ . road_cam . isEmpty ( ) ? files_ . qcamera : files_ . road_cam ;
log_path_ = files_ . rlog . isEmpty ( ) ? files_ . qlog : files_ . rlog ;
valid_ = ! log_path_ . isEmpty ( ) & & ! road_cam_path_ . isEmpty ( ) ;
if ( ! valid_ ) return ;
if ( ! load_dcam ) {
files_ . driver_cam = " " ;
}
if ( ! load_ecam ) {
files_ . wide_road_cam = " " ;
}
if ( ! QUrl ( log_path_ ) . isLocalFile ( ) ) {
for ( auto & url : { log_path_ , road_cam_path_ , files_ . driver_cam , files_ . wide_road_cam } ) {
if ( ! url . isEmpty ( ) & & ! QFile : : exists ( localPath ( url ) ) ) {
downloadFile ( url ) ;
+ + downloading_ ;
}
}
}
if ( downloading_ = = 0 ) {
QTimer : : singleShot ( 0 , this , & Segment : : load ) ;
}
}
Segment : : ~ Segment ( ) {
aborting_ = true ;
for ( auto & t : download_threads_ ) {
if ( t - > isRunning ( ) ) t - > wait ( ) ;
}
}
void Segment : : downloadFile ( const QString & url ) {
qDebug ( ) < < " download " < < url ;
download_threads_ . emplace_back ( QThread : : create ( [ = ] ( ) {
const std : : string local_file = localPath ( url ) . toStdString ( ) ;
bool ret = httpMultiPartDownload ( url . toStdString ( ) , local_file , connections_per_file , & aborting_ ) ;
if ( ret & & url = = log_path_ ) {
// pre-decompress log file.
std : : ofstream ostrm ( local_file + " _decompressed " , std : : ios : : binary ) ;
readBZ2File ( local_file , ostrm ) ;
}
if ( - - downloading_ = = 0 & & ! aborting_ ) {
load ( ) ;
}
} ) ) - > start ( ) ;
}
// load concurrency
void Segment : : load ( ) {
std : : vector < std : : future < bool > > futures ;
futures . emplace_back ( std : : async ( std : : launch : : async , [ = ] ( ) {
const std : : string bzip_file = localPath ( log_path_ ) . toStdString ( ) ;
const std : : string decompressed_file = bzip_file + " _decompressed " ;
bool is_bzip = ! util : : file_exists ( decompressed_file ) ;
log = std : : make_unique < LogReader > ( ) ;
return log - > load ( is_bzip ? bzip_file : decompressed_file , is_bzip ) ;
} ) ) ;
QString camera_files [ ] = { road_cam_path_ , files_ . driver_cam , files_ . wide_road_cam } ;
for ( int i = 0 ; i < std : : size ( camera_files ) ; + + i ) {
if ( ! camera_files [ i ] . isEmpty ( ) ) {
futures . emplace_back ( std : : async ( std : : launch : : async , [ = ] ( ) {
frames [ i ] = std : : make_unique < FrameReader > ( ) ;
return frames [ i ] - > load ( localPath ( camera_files [ i ] ) . toStdString ( ) ) ;
} ) ) ;
}
}
int success_cnt = std : : accumulate ( futures . begin ( ) , futures . end ( ) , 0 , [ = ] ( int v , auto & f ) { return f . get ( ) + v ; } ) ;
loaded_ = valid_ = ( success_cnt = = futures . size ( ) ) ;
emit loadFinished ( ) ;
}
QString Segment : : localPath ( const QUrl & url ) {
if ( url . isLocalFile ( ) ) return url . toString ( ) ;
QByteArray url_no_query = url . toString ( QUrl : : RemoveQuery ) . toUtf8 ( ) ;
return CACHE_DIR . filePath ( QString ( QCryptographicHash : : hash ( url_no_query , QCryptographicHash : : Sha256 ) . toHex ( ) ) ) ;
}