Skip to content

Commit

Permalink
Switch to the new JSON bookings API (#208)
Browse files Browse the repository at this point in the history
This uses bookings from https://atc-bookings.vatsim.net/api/booking
and drops the previously used http://vatbook.euroutepro.com/servinfo.asp 
completely. The bookings from VATBOOK are included in the new data source.
Thank you VATBOOK and Michal Rok for being with us all these years!

The setting `download/bookingsLocation` will be automatically migrated.

-----

Developer info:

VATSIM ATC Bookings API (1.0.0) docs:
https://atc-bookings.vatsim.net/api-doc

Server Code:
https://github.com/vatsimnetwork/atc-bookings

-----

Resolves: #171
  • Loading branch information
Luca0208 committed May 11, 2023
1 parent 689058e commit 65c82c5
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 136 deletions.
5 changes: 0 additions & 5 deletions src/BookedAtcDialog.cpp
Expand Up @@ -41,7 +41,6 @@ BookedAtcDialog::BookedAtcDialog(QWidget *parent) :
treeBookedAtc->setUniformRowHeights(true);
treeBookedAtc->setModel(_bookedAtcSortModel);
treeBookedAtc->sortByColumn(0, Qt::AscendingOrder);
connect(treeBookedAtc, &QAbstractItemView::clicked, this, &BookedAtcDialog::modelSelected);

// disconnect to set DateTime without being disturbed
// fixes https://sourceforge.net/p/qutescoop/tickets/5/
Expand Down Expand Up @@ -192,10 +191,6 @@ void BookedAtcDialog::performSearch() {
qDebug() << "BookedAtcDialog/performSearch() -- finished";
}

void BookedAtcDialog::modelSelected(const QModelIndex& index) {
_bookedAtcModel->modelSelected(_bookedAtcSortModel->mapToSource(index));
}

void BookedAtcDialog::on_tbPredict_clicked() {
close();
Window::instance()->actionPredict->setChecked(true);
Expand Down
1 change: 0 additions & 1 deletion src/BookedAtcDialog.h
Expand Up @@ -27,7 +27,6 @@ class BookedAtcDialog : public QDialog, private Ui::BookedAtcDialog {
private slots:
void on_dateTimeFilter_dateTimeChanged(QDateTime date);
void performSearch();
void modelSelected(const QModelIndex& index);
void on_tbPredict_clicked();
void on_spinHours_valueChanged(int val);
void on_editFilter_textChanged(QString str);
Expand Down
36 changes: 7 additions & 29 deletions src/BookedAtcDialogModel.cpp
Expand Up @@ -25,21 +25,25 @@ QVariant BookedAtcDialogModel::headerData(int section, enum Qt::Orientation orie
case 4: return QString("From");
case 5: return QString("Until");
case 6: return QString("Info");
case 7: return QString("Link");
}
}
return QVariant();
}

int BookedAtcDialogModel::columnCount(const QModelIndex&) const {
return 8;
return 7;
}

QVariant BookedAtcDialogModel::data(const QModelIndex &index, int role) const {
if(!index.isValid() || (index.row() >= rowCount(index)))
return QVariant();

if(role == Qt::DisplayRole) {
if(role == Qt::FontRole) {
BookedController* c = _controllers[index.row()];
QFont result;
result.setBold(c->isFriend());
return result;
} else if(role == Qt::DisplayRole) {
BookedController* c = _controllers[index.row()];
switch(index.column()) {
case 0: return c->label;
Expand All @@ -49,7 +53,6 @@ QVariant BookedAtcDialogModel::data(const QModelIndex &index, int role) const {
case 4: return c->starts().time().toString("HHmm'z'");
case 5: return c->ends().time().toString("HHmm'z'");
case 6: return c->bookingInfoStr;
case 7: return c->link;
}
} else if(role == Qt::EditRole) { // we are faking "EditRole" to access raw data and for sorting
BookedController* c = _controllers[index.row()];
Expand All @@ -61,7 +64,6 @@ QVariant BookedAtcDialogModel::data(const QModelIndex &index, int role) const {
case 4: return c->starts();
case 5: return c->ends();
case 6: return c->bookingInfoStr;
case 7: return c->link;
}
}

Expand All @@ -71,27 +73,3 @@ QVariant BookedAtcDialogModel::data(const QModelIndex &index, int role) const {
int BookedAtcDialogModel::rowCount(const QModelIndex&) const {
return _controllers.count();
}

void BookedAtcDialogModel::modelSelected(const QModelIndex& index) const {
if(_controllers[index.row()] != 0) {
if(!_controllers[index.row()]->link.isEmpty()) {
QUrl url = QUrl(_controllers[index.row()]->link, QUrl::TolerantMode);
if(QMessageBox::question(qApp->activeWindow(),
tr("Question"),
tr("Open %1 in your browser?")
.arg(url.toString()),
QMessageBox::Yes | QMessageBox::No
) == QMessageBox::Yes) {
if (url.isValid()) {
if(!QDesktopServices::openUrl(url))
QMessageBox::critical(qApp->activeWindow(),
tr("Error"),
tr("Could not invoke browser"));
} else
QMessageBox::critical(qApp->activeWindow(),
tr("Error"),
tr("URL %1 is invalid").arg(url.toString()));
}
}
}
}
1 change: 0 additions & 1 deletion src/BookedAtcDialogModel.h
Expand Up @@ -22,7 +22,6 @@ class BookedAtcDialogModel : public QAbstractTableModel {

public slots:
void setClients(const QList<BookedController*>& controllers);
void modelSelected(const QModelIndex& index) const;

private:
QList<BookedController*> _controllers;
Expand Down
41 changes: 16 additions & 25 deletions src/BookedController.cpp
Expand Up @@ -9,22 +9,23 @@
BookedController::BookedController(const QJsonObject& json, const WhazzupData* whazzup) :
Client(json, whazzup)
{
bookingType = json["bookingType"].toInt();
timeTo = json["timeTo"].toString();
date = json["date"].toString();
m_name = userId;

link = json["link"].toString();
if (!link.isEmpty() && !link.contains("http://")) {
link = QString("http://%1").arg(link);
}
timeFrom = json["timeFrom"].toString();
bookingType = json["type"].toString();

timeConnected = m_starts = QDateTime::fromString(json["start"].toString() + "Z", "yyyy-MM-dd HH:mm:sst");
m_ends = QDateTime::fromString(json["end"].toString() + "Z", "yyyy-MM-dd HH:mm:sst");

// computed values
switch (bookingType) {
case 0: bookingInfoStr = QString(); break;
case 1: bookingInfoStr = QString("Event"); break;
case 10: bookingInfoStr = QString("Training"); break;
if(bookingType == "booking") {
bookingInfoStr = QString();
} else if (bookingType == "event") {
bookingInfoStr = QString("Event");
} else if (bookingType == "training") {
bookingInfoStr = QString("Training");
} else {
bookingInfoStr = bookingType;
}

if (label.right(5) == "_ATIS") {
facilityType = 2;
} else if (label.right(4) == "_DEL") {
Expand All @@ -40,8 +41,6 @@ BookedController::BookedController(const QJsonObject& json, const WhazzupData* w
} else if (label.right(4) == "_FSS") {
facilityType = 7;
}

timeConnected = starts();
}

QString BookedController::facilityString() const {
Expand All @@ -59,17 +58,9 @@ QString BookedController::facilityString() const {
}

QDateTime BookedController::starts() const {
return QDateTime(
QDate::fromString(date, QString("yyyyMMdd")),
QTime::fromString(timeFrom, QString("HHmm")),
Qt::UTC
);
return m_starts;
}

QDateTime BookedController::ends() const {
return QDateTime(
QDate::fromString(date, QString("yyyyMMdd")),
QTime::fromString(timeTo, QString("HHmm")),
Qt::UTC
);
return m_ends;
}
7 changes: 3 additions & 4 deletions src/BookedController.h
Expand Up @@ -26,13 +26,12 @@ class BookedController: public Client {

int facilityType;

// Booking values
QString link, bookingInfoStr, timeFrom, timeTo, date, eventLink;
int bookingType;
QString bookingInfoStr, bookingType;

QDateTime starts() const;
QDateTime ends() const;
private:
protected:
QDateTime m_starts, m_ends;
};

#endif /*BOOKEDCONTROLLER_H_*/
13 changes: 12 additions & 1 deletion src/Settings.cpp
Expand Up @@ -7,6 +7,7 @@
#include "Whazzup.h"
#include "Window.h"
#include "PilotDetails.h"
#include "GuiMessage.h"
#include "ControllerDetails.h"
#include "AirportDetails.h"
#include "Client.h"
Expand All @@ -17,7 +18,7 @@ QSettings* Settings::instance() {
if(settingsInstance == 0) {
settingsInstance = new QSettings();

const int requiredSettingsVersion = 2;
const int requiredSettingsVersion = 3;
int currentSettingsVersion = settingsInstance->value("settings/version", 0).toInt();
if (currentSettingsVersion < requiredSettingsVersion) {
if(currentSettingsVersion < 1) {
Expand Down Expand Up @@ -54,6 +55,16 @@ QSettings* Settings::instance() {
settingsInstance->setValue("friends/friendList", friendList);
currentSettingsVersion = 2;
}
if(currentSettingsVersion < 3) {
qDebug() << "Starting migration 2 -> 3";
if(settingsInstance->value("download/bookingsLocation").toString() != "http://vatbook.euroutepro.com/servinfo.asp") {
GuiMessages::criticalUserInteraction(QString("You have set the location for bookings to %1.\nThis is different from the old default location. Due to an update in QuteScoop the old format for bookings is no longer supported.\n\nYou will be migrated to the new default VATSIM bookings URL.")
.arg(settingsInstance->value("download/bookingsLocation").toString()),
"Update of bookings format");
}
settingsInstance->setValue("download/bookingsLocation", "https://atc-bookings.vatsim.net/api/booking");
currentSettingsVersion = 3;
}
settingsInstance->setValue("settings/version", currentSettingsVersion);
}
}
Expand Down
79 changes: 9 additions & 70 deletions src/WhazzupData.cpp
Expand Up @@ -28,7 +28,9 @@ WhazzupData::WhazzupData(QByteArray* bytes, WhazzupType type) :
_dataType = type;
int reloadInMin = Settings::downloadInterval();
QJsonDocument data = QJsonDocument::fromJson(*bytes);
if (!data.isNull() && type == WHAZZUP) {
if (data.isNull()) {
qDebug() << "Couldn't parse JSON";
} else if (type == WHAZZUP) {
QJsonObject json = data.object();
if (json.contains("general") && json["general"].isObject()) {
QJsonObject generalObject = json["general"].toObject();
Expand Down Expand Up @@ -97,76 +99,13 @@ WhazzupData::WhazzupData(QByteArray* bytes, WhazzupType type) :
}
}
} else if (type == ATCBOOKINGS) {
// ATC Bookings are still in the old format, however we only need the CLIENTS and GENERAL sections
enum ParserState {STATE_NONE, STATE_GENERAL, STATE_CLIENTS};
ParserState state = STATE_NONE;
foreach (QString line, bytes->split('\n')) {
line = line.trimmed();
if (line.isEmpty()) {
continue;
}

if (line.startsWith(';')) {
continue;
}

if (line.startsWith("!")) {
if (line.startsWith("!CLIENTS")) {
state = STATE_CLIENTS;
}
else if (line.startsWith("!GENERAL")) {
state = STATE_GENERAL;
}
else {
state = STATE_NONE;
}

continue;
}

switch (state) {
case STATE_CLIENTS: {
QStringList list = line.split(':');

if (list.size() < 38) {
continue;
}

if (list[3] != "ATC") {
continue;
}
// Create a JSON Object containing the required fields
QJsonObject controllerObject;
controllerObject["callsign"] = list[0];
controllerObject["cid"] = list[1].toInt();
controllerObject["name"] = list[2];

// Bookings only:
controllerObject["bookingType"] = list[4].toInt();
controllerObject["timeTo"] = list[14];
controllerObject["date"] = list[16];
controllerObject["link"] = list[35];
controllerObject["timeFrom"] = list[37];

BookedController* bc = new BookedController(controllerObject, this);
bookedControllers.append(bc);
}
break;
case STATE_GENERAL: {
QStringList list = line.split("=");
if (list.size() != 2) {
continue;
}
if (line.startsWith("UPDATE")) {
bookingsTime = QDateTime::fromString(list[1].trimmed(), "yyyyMMddHHmmss");
bookingsTime.setTimeSpec(Qt::UTC);
}
}
break;
case STATE_NONE:
break;
}
QJsonArray json = data.array();
for (int i = 0; i < json.size(); ++i) {
QJsonObject bookedControllerJson = json[i].toObject();
BookedController* bc = new BookedController(bookedControllerJson, this);
bookedControllers.append(bc);
}
bookingsTime = QDateTime::currentDateTime();
} else {
// Try again in 15 seconds
updateEarliest = QDateTime::currentDateTime().addSecs(15);
Expand Down

0 comments on commit 65c82c5

Please sign in to comment.