diff --git a/selfdrive/ui/replay/replay.cc b/selfdrive/ui/replay/replay.cc index 5795eae3ea..1ed37d1fa9 100644 --- a/selfdrive/ui/replay/replay.cc +++ b/selfdrive/ui/replay/replay.cc @@ -70,7 +70,7 @@ bool Replay::load() { } void Replay::start(int seconds) { - seekTo(seconds, false); + seekTo(route_->identifier().segment_id * 60 + seconds, false); } void Replay::updateEvents(const std::function &lambda) { diff --git a/selfdrive/ui/replay/route.cc b/selfdrive/ui/replay/route.cc index 206970009e..71be470ca2 100644 --- a/selfdrive/ui/replay/route.cc +++ b/selfdrive/ui/replay/route.cc @@ -9,69 +9,62 @@ #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 (data_dir_.isEmpty()) { - return loadFromServer(); - } else { - return loadFromLocal(); + if (route_.str.isEmpty()) { + qInfo() << "invalid route format"; + return false; } + return data_dir_.isEmpty() ? loadFromServer() : loadFromLocal(); } bool Route::loadFromServer() { 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(); + 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_ + "/files"); - loop.exec(); - return ret; + http.sendRequest("https://api.commadotai.com/v1/route/" + route_.str + "/files"); + return loop.exec(); } 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()) { + 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 true; + return !segments_.empty(); } bool Route::loadFromLocal() { - QString prefix = route_.split('|').last(); - if (prefix.isEmpty()) return false; - QDir log_dir(data_dir_); - QStringList folders = log_dir.entryList(QDir::Dirs | QDir::NoDot | QDir::NoDotDot, QDir::NoSort); - if (folders.isEmpty()) return false; - - for (auto folder : folders) { - int seg_num_pos = folder.lastIndexOf("--"); - if (seg_num_pos != -1) { - const int seg_num = folder.mid(seg_num_pos + 2).toInt(); + 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 (auto f : segment_dir.entryList(QDir::Files)) { + for (const auto &f : segment_dir.entryList(QDir::Files)) { addFileToSegment(seg_num, segment_dir.absoluteFilePath(f)); } } } - return true; + return !segments_.empty(); } void Route::addFileToSegment(int n, const QString &file) { @@ -97,7 +90,7 @@ Segment::Segment(int n, const SegmentFile &files, bool load_dcam, bool load_ecam static std::once_flag once_flag; std::call_once(once_flag, [=]() { if (!CACHE_DIR.exists()) QDir().mkdir(CACHE_DIR.absolutePath()); }); - // the order is [RoadCam, DriverCam, WideRoadCam, log]. fallback to qcamera/qlog + // [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 : "", diff --git a/selfdrive/ui/replay/route.h b/selfdrive/ui/replay/route.h index 7944a1ec29..f0609c5ef3 100644 --- a/selfdrive/ui/replay/route.h +++ b/selfdrive/ui/replay/route.h @@ -9,6 +9,13 @@ const QDir CACHE_DIR(util::getenv("COMMA_CACHE", "/tmp/comma_download_cache/").c_str()); +struct RouteIdentifier { + QString dongle_id; + QString timestamp; + int segment_id; + QString str; +}; + struct SegmentFile { QString rlog; QString qlog; @@ -20,18 +27,20 @@ struct SegmentFile { class Route { public: - Route(const QString &route, const QString &data_dir = {}) : route_(route), data_dir_(data_dir) {}; + Route(const QString &route, const QString &data_dir = {}); bool load(); - inline const QString &name() const { return route_; }; + inline const QString &name() const { return route_.str; } + inline const RouteIdentifier &identifier() const { return route_; } inline const std::map &segments() const { return segments_; } inline const SegmentFile &at(int n) { return segments_.at(n); } + static RouteIdentifier parseRoute(const QString &str); protected: bool loadFromLocal(); bool loadFromServer(); bool loadFromJson(const QString &json); void addFileToSegment(int seg_num, const QString &file); - QString route_; + RouteIdentifier route_ = {}; QString data_dir_; std::map segments_; };