Rewrote flights.py and maintenance.py.

pull/586/head
jprochazka 2024-07-12 23:05:48 -04:00
rodzic f6533814b5
commit d5c698c9f5
11 zmienionych plików z 309 dodań i 257 usunięć

Wyświetl plik

@ -242,7 +242,7 @@ if [[ "${ADVANCED}" = "true" ]] ; then
case "${DATABASEENGINE}" in
"MySQL")
CheckPackage mariadb-client
CheckPackage python3-mysql.connector
CheckPackage python3-mysqldb
CheckPackage php${DISTRO_PHP_VERSION}-mysql
;;
"SQLite")

Wyświetl plik

@ -1,50 +1,18 @@
#!/bin/bash
#####################################################################################
# ADS-B RECEIVER #
#####################################################################################
# #
# This script is not meant to be executed directly. #
# Instead execute install.sh to begin the installation process. #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# Copyright (c) 2015-2024 Joseph A. Prochazka #
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy #
# of this software and associated documentation files (the "Software"), to deal #
# in the Software without restriction, including without limitation the rights #
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #
# copies of the Software, and to permit persons to whom the Software is #
# furnished to do so, subject to the following conditions: #
# #
# The above copyright notice and this permission notice shall be included in all #
# copies or substantial portions of the Software. #
# #
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #
# SOFTWARE. #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
## VARIABLES
PORTAL_PYTHON_DIRECTORY="${RECEIVER_BUILD_DIRECTORY}/portal/python"
PYTHON_PATH=`which python3`
python_path=`which python3`
## SETUP FLIGHT LOGGING
echo -e ""
echo -e "\e[95m Setting up flight logging...\e[97m"
echo -e "\e[95m Setting up portal flight logging and maintenance...\e[97m"
echo -e ""
# Create the cron jobs responsible for logging and maintenance.
echo -e "\e[94m Creating the maintenance maintenance script...\e[97m"
echo -e "\e[94m Creating the portal script cron file...\e[97m"
sudo tee /etc/cron.d/adsb-receiver-flight-logging > /dev/null <<EOF
* * * * * root ${PYTHON_PATH} ${PORTAL_PYTHON_DIRECTORY}/flights.py
30 * * * * root ${PYTHON_PATH} ${PORTAL_PYTHON_DIRECTORY}/maintenance.py
* * * * * root ${python_path} ${RECEIVER_BUILD_DIRECTORY}/portal/python/flights.py
0 0 * * * root ${python_path} ${RECEIVER_BUILD_DIRECTORY}/portal/python/maintenance.py
EOF

Wyświetl plik

@ -1,40 +1,9 @@
#!/bin/bash
#####################################################################################
# ADS-B RECEIVER #
#####################################################################################
# #
# This script is not meant to be executed directly. #
# Instead execute install.sh to begin the installation process. #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# Copyright (c) 2015-2024, Joseph A. Prochazka #
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy #
# of this software and associated documentation files (the "Software"), to deal #
# in the Software without restriction, including without limitation the rights #
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #
# copies of the Software, and to permit persons to whom the Software is #
# furnished to do so, subject to the following conditions: #
# #
# The above copyright notice and this permission notice shall be included in all #
# copies or substantial portions of the Software. #
# #
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #
# SOFTWARE. #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
## SOFTWARE VERSIONS
# The ADS-B Receiver Project
PROJECT_VERSION="2.8.3"
PROJECT_VERSION="2.8.4"
# FlightAware
DUMP1090_FA_VERSION="9.0"

Wyświetl plik

@ -1,33 +1,5 @@
<?php
/////////////////////////////////////////////////////////////////////////////////////
// ADS-B RECEIVER PORTAL //
// =============================================================================== //
// Copyright and Licensing Information: //
// //
// The MIT License (MIT) //
// //
// Copyright (c) 2015-2024 Joseph A. Prochazka //
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy //
// of this software and associated documentation files (the "Software"), to deal //
// in the Software without restriction, including without limitation the rights //
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //
// copies of the Software, and to permit persons to whom the Software is //
// furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in all //
// copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //
// SOFTWARE. //
/////////////////////////////////////////////////////////////////////////////////////
class template {
// PUT THE TEMPLATE TOGETHER
@ -37,7 +9,7 @@
// Check if the portal is installed or needs upgraded.
$thisVersion = "2.8.3";
$thisVersion = "2.8.4";
if (!file_exists($_SERVER['DOCUMENT_ROOT']."/classes/settings.class.php")) {
header ("Location: /install/install.php");

Wyświetl plik

@ -29,7 +29,7 @@
/////////////////////////////////////////////////////////////////////////////////////
// The most current stable release.
$thisVersion = "2.8.3";
$thisVersion = "2.8.4";
// Begin the upgrade process if this release is newer than what is installed.
if (file_exists($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR."classes".DIRECTORY_SEPARATOR."settings.class.php")) {
@ -233,7 +233,7 @@ EOF;
flight VARCHAR(10) NOT NULL);';
$positionsSql = 'CREATE TABLE '.$dbPrefix.'positions (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
flight BIGINT NOT NULL,
flight BIGINT NULL,
aircraft BIGINT NOT NULL,
time datetime NOT NULL,
message INT NOT NULL,

Wyświetl plik

@ -48,7 +48,7 @@
try {
// Update the version and patch settings..
// Update the version and patch settings.
$common->updateSetting("version", "2.8.3");
$common->updateSetting("patch", "");

Wyświetl plik

@ -0,0 +1,75 @@
<?php
/////////////////////////////////////////////////////////////////////////////////////
// ADS-B RECEIVER PORTAL //
// =============================================================================== //
// Copyright and Licensing Information: //
// //
// The MIT License (MIT) //
// //
// Copyright (c) 2015 Joseph A. Prochazka //
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy //
// of this software and associated documentation files (the "Software"), to deal //
// in the Software without restriction, including without limitation the rights //
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //
// copies of the Software, and to permit persons to whom the Software is //
// furnished to do so, subject to the following conditions: //
// //
// The above copyright notice and this permission notice shall be included in all //
// copies or substantial portions of the Software. //
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //
// SOFTWARE. //
/////////////////////////////////////////////////////////////////////////////////////
///////////////////////
// UPGRADE TO V2.8.4
///////////////////////
// --------------------------------------------------------
// Updates the version setting to 2.8.4.
// --------------------------------------------------------
$results = upgrade();
exit(json_encode($results));
function upgrade() {
require_once($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR."classes".DIRECTORY_SEPARATOR."common.class.php");
require_once($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR."classes".DIRECTORY_SEPARATOR."settings.class.php");
$common = new common();
$settings = new settings();
try {
// Update the version and patch settings..
$common->updateSetting("version", "2.8.4");
$common->updateSetting("patch", "");
// Allow NULL flights in the positions table.
$dbh = $common->pdoOpen();
$sql = "ALTER TABLE ".$settings::db_prefix."positions MODIFY flight BIGINT NULL";
$sth = $dbh->prepare($sql);
$sth->execute();
$sth = NULL;
// The upgrade process completed successfully.
$results['success'] = TRUE;
$results['message'] = "Upgrade to v2.8.4 successful.";
return $results;
} catch(Exception $e) {
// Something went wrong during this upgrade process.
$results['success'] = FALSE;
$results['message'] = $e->getMessage();
return $results;
}
}
?>

Wyświetl plik

@ -33,7 +33,7 @@
$common = new common();
// The most current stable release.
$thisVersion = "2.8.3";
$thisVersion = "2.8.4";
// Begin the upgrade process if this release is newer than what is installed.
if ($common->getSetting("version") == $thisVersion) {
@ -213,6 +213,15 @@
$version = "2.8.3";
}
// UPGRADE TO V2.8.4
if ($common->getSetting("version") == "2.8.3" && $success) {
$json = file_get_contents("http://localhost/install/upgrade-v2.8.4.php");
$results = json_decode($json, TRUE);
$success = $results['success'];
$message = $results['message'];
$version = "2.8.4";
}
require_once($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR."admin".DIRECTORY_SEPARATOR."includes".DIRECTORY_SEPARATOR."header.inc.php");
// Display the instalation wizard.

Wyświetl plik

@ -302,8 +302,8 @@
var i,x,y,ARRcookies=document.cookie.split(";");
for (i=0;i<ARRcookies.length;i++)
{
x=ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));
y=ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1);
x=ARRcookies[i].substr(0,ARRcookies[i].toString().indexOf("="));
y=ARRcookies[i].substr(ARRcookies[i].toString().indexOf("=")+1);
x=x.replace(/^\s+|\s+$/g,"");
if (x==c_name)
{

Wyświetl plik

@ -1,38 +1,41 @@
#!/usr/bin/python
import datetime
import fcntl
import json
import logging
import MySQLdb
import os
import time
import sqlite3
from urllib import urlopen
now = None
from datetime import datetime
from time import sleep
from urllib.request import urlopen
class AircraftProcessor(object):
# Log infromation to console
def log(self, string):
#print(f'[{datetime.now().strftime("%Y/%m/%d %H:%M:%S")}] {string}') # uncomment to enable debug logging
return
# Create database connection
def create_connection():
def create_connection(self):
self.log("Setting up database connection")
with open(os.path.dirname(os.path.realpath(__file__)) + '/config.json') as config_file:
config = json.load(config_file)
match config["database"]["type"].lower():
case 'mysql':
import mysql.connector
return mysql.connector.connect(
return MySQLdb.connect(
host=config["database"]["host"],
user=config["database"]["user"],
password=config["database"]["passwd"],
database=config["database"]["db"]
passwd=config["database"]["passwd"],
db=config["database"]["db"]
)
case 'sqlite':
import sqlite3
return sqlite3.connect(config["database"]["db"])
# Read JSON supplied by dump1090
def read_json():
def read_json(self):
self.log("Reading aircraft.json")
try:
raw_json = urlopen('http://127.0.0.1/dump1090/data/aircraft.json')
json_object = json.load(raw_json)
@ -42,15 +45,18 @@ class AircraftProcessor(object):
return
# Begin processing data retrived from dump1090
def process_all_aircraft(self, all_aircraft):
def process_all_aircraft(self):
data = self.read_json()
aircraft_data = data["aircraft"]
connection = self.create_connection()
self.cursor = connection.cursor()
if len(aircraft_data) == 0:
self.log(f'There is no aircraft data to process at this time')
return
for aircraft in all_aircraft:
self.log(f'Begining to proocess {len(aircraft_data)} aircraft')
for aircraft in aircraft_data:
self.process_aircraft(aircraft)
connection.commit()
connection.close()
return
@ -59,117 +65,170 @@ class AircraftProcessor(object):
def process_aircraft(self, aircraft):
tracked=False
aircraft_id=None
try:
self.cursor.execute("SELECT COUNT(*) FROM aircraft WHERE icao = %s", (aircraft["hex"],))
if self.cursor.fetchone()[0] > 0:
cursor.execute("SELECT COUNT(*) FROM adsb_aircraft WHERE icao = %s", (aircraft["hex"],))
if cursor.fetchone()[0] > 0:
tracked=True
except Exception as ex:
logging.error(f"Error encountered while checking if aircraft '{aircraft["hex"]}' has already been added", exc_info=ex)
logging.error(f'Error encountered while checking if aircraft {aircraft["hex"]} has already been added', exc_info=ex)
return
if tracked:
query = "UPDATE aircraft SET lastSeen = %s WHERE icao = %s",
parameters = (now, aircraft["hex"])
error_message = f"Error encountered while trying to update aircraft '{aircraft["hex"]}'"
self.log(f'Updating aircraft ICAO {aircraft["hex"]}')
try:
cursor.execute(
"UPDATE adsb_aircraft SET lastSeen = %s WHERE icao = %s",
(now, aircraft["hex"])
)
connection.commit()
cursor.execute(
"SELECT id FROM adsb_aircraft WHERE icao = %s",
(aircraft["hex"],)
)
aircraft_id = cursor.fetchone()[0]
except Exception as ex:
logging.error(f'Error encountered while trying to update aircraft {aircraft["hex"]}', exc_info=ex)
return
else:
query = "INSERT INTO aircraft (icao, firstSeen, lastSeen) VALUES (%s, %s, %s)",
parameters = (aircraft["hex"], now, now)
error_message = f"Error encountered while trying to insert aircraft '{aircraft["hex"]}'"
try:
self.cursor.execute(query, parameters)
aircraft_id = self.cursor.lastrowid
except Exception as ex:
logging.error(error_message, exc_info=ex)
return
self.log(f'Inserting aircraft ICAO {aircraft["hex"]}')
try:
cursor.execute(
"INSERT INTO adsb_aircraft (icao, firstSeen, lastSeen) VALUES (%s, %s, %s)",
(aircraft["hex"], now, now)
)
connection.commit()
aircraft_id = cursor.lastrowid
except Exception as ex:
logging.error(f'Error encountered while trying to insert aircraft {aircraft["hex"]}', exc_info=ex)
return
if 'flight' in aircraft:
self.process_flight(aircraft_id, aircraft)
else:
self.process_positions(aircraft_id , None, aircraft)
return
# Process the flight
def process_flight(self, aircraft_id, aircraft):
tracked=False
try:
self.cursor.execute("SELECT COUNT(*) FROM flights WHERE flight = %s", (aircraft["flight"],))
if self.cursor.fetchone()[0] > 0:
tracked=True
except Exception as ex:
logging.error(f"Error encountered while checking if flight '{aircraft["flight"]}' has already been added", exc_info=ex)
return
if 'flight' in aircraft:
flight = aircraft["flight"].strip()
tracked=False
try:
cursor.execute("SELECT COUNT(*) FROM adsb_flights WHERE flight = %s", (flight,))
if cursor.fetchone()[0] > 0:
tracked=True
except Exception as ex:
logging.error(f'Error encountered while checking if flight {flight} has already been added', exc_info=ex)
return
if tracked:
self.log(f' Updating flight {flight} assigned to aircraft ICAO {aircraft["hex"]}')
try:
cursor.execute(
"UPDATE adsb_flights SET lastSeen = %s WHERE flight = %s",
(now, flight)
)
connection.commit()
cursor.execute(
"SELECT id FROM adsb_flights WHERE flight = %s",
(flight,)
)
flight_id = cursor.fetchone()[0]
except Exception as ex:
logging.error(f'Error encountered while trying to update flight {flight}', exc_info=ex)
return
else:
self.log(f'Inserting flight {flight} assigned to aircraft ICAO {aircraft["hex"]}')
try:
cursor.execute(
"INSERT INTO adsb_flights (aircraft, flight, firstSeen, lastSeen) VALUES (%s, %s, %s, %s)",
(aircraft_id, flight, now, now)
)
connection.commit()
flight_id = cursor.lastrowid
except Exception as ex:
logging.error(f'Error encountered while trying to insert flight {flight}', exc_info=ex)
return
if tracked:
query = "UPDATE flights SET lastSeen = %s WHERE icao = %s",
parameters = (now, aircraft["flight"])
error_message = f"Error encountered while trying to update flight '{aircraft["flight"]}'"
else:
query = "INSERT INTO flights (aircraft, flight, firstSeen, lastSeen) VALUES (%s, %s, %s, %s)",
parameters = (aircraft_id, aircraft["flight"], now, now)
error_message = f"Error encountered while trying to insert flight '{aircraft["flight"]}'"
self.log(f' Aircraft ICAO {aircraft["hex"]} was not assigned a flight')
try:
self.cursor.execute(query, parameters)
flight_id = self.cursor.lastrowid
except Exception as ex:
logging.error(error_message, exc_info=ex)
return
position_keys = ('lat', 'lon', 'nav_altitude', 'gs', 'track', 'geom_rate', 'hex')
if (all(key in aircraft for key in position_keys) and aircraft["altitude"] != "ground"):
self.process_positions(aircraft_id, flight_id, aircraft)
self.process_positions(aircraft_id, flight_id, aircraft)
return
# Process positions
def process_positions(self, aircraft_id , flight_id, aircraft):
tracked=False
try:
self.cursor.execute("SELECT COUNT(*) FROM positions WHERE flight = %s AND message = %s", (flight_id, aircraft["messages"]))
if self.cursor.fetchone()[0] > 0:
tracked=True
except Exception as ex:
logging.error(f"Error encountered while checking if position has already been added for message ID '{aircraft["messages"]}' related to flight '{flight_id}'", exc_info=ex)
return
if tracked:
return
position_keys = ('lat', 'lon', 'alt_baro', 'gs', 'track', 'geom_rate', 'hex')
if (all(key in aircraft for key in position_keys)):
squawk = None
if 'squawk' in aircraft:
squawk = aircraft["squawk"]
tracked=False
try:
cursor.execute("SELECT COUNT(*) FROM adsb_positions WHERE flight = %s AND message = %s", (flight_id, aircraft["messages"]))
if cursor.fetchone()[0] > 0:
tracked=True
except Exception as ex:
logging.error(f'Error encountered while checking if position has already been added for message ID {aircraft["messages"]} related to flight {flight_id}', exc_info=ex)
return
if tracked:
return
squawk = None
if 'squawk' in aircraft:
squawk = aircraft["squawk"]
altitude = aircraft["alt_baro"]
if 'alt_geom' in aircraft:
altitude = aircraft["alt_geom"]
try:
if flight_id is None:
self.log(f' Inserting position for aircraft ICAO {aircraft["hex"]}')
else:
self.log(f' Inserting position for aircraft ICAO {aircraft["hex"]} assigned flight {flight_id}')
cursor.execute(
"INSERT INTO adsb_positions (flight, time, message, squawk, latitude, longitude, track, altitude, verticleRate, speed, aircraft) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
(flight_id, now, aircraft["messages"], squawk, aircraft["lat"], aircraft["lon"], aircraft["track"], altitude, aircraft["geom_rate"], aircraft["gs"], aircraft_id)
)
connection.commit()
except Exception as ex:
logging.error(f'Error encountered while inserting position data for message ID {aircraft["messages"]} related to flight {flight_id}', exc_info=ex)
return
else:
self.log(f' Data required to insert position data for Aircraft ICAO {aircraft["hex"]} is not present')
try:
self.cursor.execute(
"INSERT INTO positions (flight, time, message, squawk, latitude, longitude, track, altitude, verticleRate, speed, aircraft) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
(flight_id, now, aircraft["messages"], squawk, aircraft["lat"], aircraft["lon"], aircraft["track"], aircraft["nav_altitude"], aircraft["geom_rate"], aircraft["gs"], aircraft_id)
)
flight_id = self.cursor.lastrowid
except Exception as ex:
logging.error(f"Error encountered while inserting position data for message ID '{aircraft["messages"]}' related to flight '{flight_id}'", exc_info=ex)
return
return
if __name__ == "__main__":
processor = AircraftProcessor()
logging.info(f"Beginning flight recording job on {datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")}")
processor.log("-- CHECKING IF FLIGHT RECORDER JOB IS ALREADY RUNNING")
# Do not allow another instance of the job to run
lock_file = open('/tmp/flights.py.lock','w')
try:
fcntl.flock(lock_file, fcntl.LOCK_EX|fcntl.LOCK_NB)
except (IOError, OSError):
logging.info('Another instance already running')
processor.log("-- ANOTHER INSTANCE OF THIS JOB IS RUNNING")
quit()
# Begin flight recording job
lock_file.write('%d\n'%os.getpid())
while True:
now = datetime.datetime.now()
data = processor.read_json()
processor.process_all_aircraft(data["aircraft"])
logging.info(f"Flight recording job ended on {datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")}")
time.sleep(15)
processor.log("-- BEGINING FLIGHT RECORDER JOB")
# Set up database connection
connection = processor.create_connection()
cursor = connection.cursor()
# Begin flight recording job
now = datetime.now()
processor.process_all_aircraft()
processor.log("-- FLIGHT RECORD JOB COMPLETE")
processor.log("SLEEPING 15 SECONDS BEFORE NEXT RUN")
sleep(15)

Wyświetl plik

@ -1,66 +1,64 @@
#!/usr/bin/python
import fcntl
import json
import logging
import MySQLdb
import os
import sqlite3
import time
import yaml
from datetime import datetime, timedelta
config = yaml.safe_load(open("config.yml"))
class MaintenanceProcessor(object):
# Log infromation to console
def log(self, string):
print(f'[{datetime.now().strftime("%Y/%m/%d %H:%M:%S")}] {string}') # uncomment to enable debug logging
return
# Create database connection
def create_connection():
def create_connection(self):
self.log("Setting up database connection")
with open(os.path.dirname(os.path.realpath(__file__)) + '/config.json') as config_file:
config = json.load(config_file)
match config["database"]["type"].lower():
case 'mysql':
import mysql.connector
return mysql.connector.connect(
return MySQLdb.connect(
host=config["database"]["host"],
user=config["database"]["user"],
password=config["database"]["passwd"],
database=config["database"]["db"]
passwd=config["database"]["passwd"],
db=config["database"]["db"]
)
case 'sqlite':
import sqlite3
return sqlite3.connect(config["database"]["db"])
# Begin maintenance
# Begin maintenance
def begin_maintenance(self):
connection = self.create_connection()
self.cursor = connection.cursor()
self.log("Getting maintenance settings from the database")
purge_old_aircraft = False
try:
self.cursor.execute("SELECT value FROM settings WHERE name 'purgeAircraft'")
result = self.cursor.fetchone()[0]
cursor.execute("SELECT value FROM adsb_settings WHERE name = 'purgeAircraft'")
result = cursor.fetchone()[0]
purge_old_aircraft = result.lower() in ['true', '1']
except Exception as ex:
logging.error(f"Error encountered while getting value for setting 'purgeAircraft'", exc_info=ex)
logging.error(f"Error encountered while getting value for setting purgeAircraft", exc_info=ex)
return
if purge_old_aircraft:
cutoff_date = datetime.now() - timedelta(years = 20)
try:
self.cursor.execute("SELECT value FROM settings WHERE name 'purgeDaysOld'")
days_to_save = self.cursor.fetchone()[0]
cursor.execute("SELECT value FROM adsb_settings WHERE name = 'purgeDaysOld'")
days_to_save = cursor.fetchone()[0]
except Exception as ex:
logging.error(f"Error encountered while getting value for setting 'purgeDaysOld'", exc_info=ex)
logging.error(f"Error encountered while getting value for setting purgeDaysOld", exc_info=ex)
return
cutoff_date = datetime.now() - timedelta(days = days_to_save)
self.purge_aircraft(cutoff_date)
self.purge_flights(cutoff_date)
self.purge_positions(cutoff_date)
else:
self.log("Maintenance is disabled")
connection.commit()
connection.close()
return
@ -68,10 +66,10 @@ class MaintenanceProcessor(object):
# Remove aircraft not seen since the specified date
def purge_aircraft(self, cutoff_date):
try:
self.cursor.execute("SELECT id FROM aircraft WHERE lastSeen < %s", (cutoff_date,))
aircraft_ids = self.cursor.fetchall()
cursor.execute("SELECT id FROM adsb_aircraft WHERE lastSeen < %s", (cutoff_date,))
aircraft_ids = cursor.fetchall()
except Exception as ex:
logging.error(f"Error encountered while getting aircraft IDs not seen since '{cutoff_date}'", exc_info=ex)
logging.error(f"Error encountered while getting aircraft IDs not seen since {cutoff_date}", exc_info=ex)
return
if len(aircraft_ids) > 0:
@ -79,11 +77,11 @@ class MaintenanceProcessor(object):
aircraft_id_params = {'id': id}
try:
self.cursor.execute("DELETE FROM aircraft WHERE id IN %(t)s", aircraft_id_params)
cursor.execute("DELETE FROM adsb_aircraft WHERE id IN %(t)s", aircraft_id_params)
except Exception as ex:
logging.error(f"Error deleting aircraft not seen since '{cutoff_date}'", exc_info=ex)
logging.error(f"Error deleting aircraft not seen since {cutoff_date}", exc_info=ex)
return
self.purge_flights_related_to_aircraft(aircraft_id_params, cutoff_date)
self.purge_positions_related_to_aircraft(aircraft_id_params, cutoff_date)
@ -92,30 +90,30 @@ class MaintenanceProcessor(object):
# Remove flights related to aircraft not seen since the specified date
def purge_flights_related_to_aircraft(self, aircraft_id_params, cutoff_date):
try:
self.cursor.execute("DELETE FROM flights WHERE aircraft = %(t)s", aircraft_id_params)
cursor.execute("DELETE FROM adsb_flights WHERE aircraft = %(t)s", aircraft_id_params)
except Exception as ex:
logging.error(f"Error deleting flights related to aircraft not seen since '{cutoff_date}'", exc_info=ex)
logging.error(f"Error deleting flights related to aircraft not seen since {cutoff_date}", exc_info=ex)
return
return
# Remove positions related to aircraft not seen since the specified date
def purge_positions_related_to_aircraft(self, aircraft_id_params, cutoff_date):
try:
self.cursor.execute("DELETE FROM positions WHERE aircraft = %(t)s", aircraft_id_params)
cursor.execute("DELETE FROM adsb_positions WHERE aircraft = %(t)s", aircraft_id_params)
except Exception as ex:
logging.error(f"Error deleting positions related to aircraft not seen since '{cutoff_date}'", exc_info=ex)
logging.error(f"Error deleting positions related to aircraft not seen since {cutoff_date}", exc_info=ex)
return
return
# Remove positions older than the specified date
def purge_flights(self, cutoff_date):
try:
self.cursor.execute("SELECT id FROM flights WHERE lastSeen < %s", (cutoff_date,))
flight_ids = self.cursor.fetchall()
cursor.execute("SELECT id FROM adsb_flights WHERE lastSeen < %s", (cutoff_date,))
flight_ids = cursor.fetchall()
except Exception as ex:
logging.error(f"Error encountered while getting aircraft IDs not seen since '{cutoff_date}'", exc_info=ex)
logging.error(f"Error encountered while getting aircraft IDs not seen since {cutoff_date}", exc_info=ex)
return
if len(flight_ids) > 0:
@ -123,51 +121,53 @@ class MaintenanceProcessor(object):
flight_id_params = {'id': id}
try:
self.cursor.execute("DELETE FROM flights WHERE id IN %(t)s", flight_id_params)
cursor.execute("DELETE FROM adsb_flights WHERE id IN %(t)s", flight_id_params)
except Exception as ex:
logging.error(f"Error deleting flights older than the cut off date of '{cutoff_date}'", exc_info=ex)
logging.error(f"Error deleting flights older than the cut off date of {cutoff_date}", exc_info=ex)
return
self.purge_positions_related_to_flights(flight_id_params, cutoff_date)
return
# Remove positions related to aircraft not seen since the specified date
def purge_positions_related_to_flights(self, flight_id_params, cutoff_date):
try:
self.cursor.execute("DELETE FROM positions WHERE flight = %(t)s", flight_id_params)
cursor.execute("DELETE FROM adsb_positions WHERE flight = %(t)s", flight_id_params)
except Exception as ex:
logging.error(f"Error deleting positions related to flights not seen since '{cutoff_date}'", exc_info=ex)
logging.error(f"Error deleting positions related to flights not seen since {cutoff_date}", exc_info=ex)
return
return
# Remove positions older than the specified date
def purge_positions(self, cutoff_date):
try:
self.cursor.execute("DELETE FROM positions WHERE time < %s", (cutoff_date,))
cursor.execute("DELETE FROM adsb_positions WHERE time < %s", (cutoff_date,))
except Exception as ex:
logging.error(f"Error deleting positions older than the cut off date of '{cutoff_date}'", exc_info=ex)
logging.error(f"Error deleting positions older than the cut off date of {cutoff_date}", exc_info=ex)
return
return
if __name__ == "__main__":
processor = MaintenanceProcessor()
logging.info(f"Beginning maintenance job on {datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")}")
processor.log("-- BEGINING PORTAL MAINTENANCE JOB")
# Do not allow another instance of the job to run
lock_file = open('/tmp/maintenance.py.lock','w')
try:
fcntl.flock(lock_file, fcntl.LOCK_EX|fcntl.LOCK_NB)
except (IOError, OSError):
logging.info('Another instance already running')
processor.log("-- ANOTHER INSTANCE OF THIS JOB IS RUNNING")
quit()
# Set up database connection
connection = processor.create_connection()
cursor = connection.cursor()
# Begin maintenance job
lock_file.write('%d\n'%os.getpid())
while True:
processor.begin_maintenance()
logging.info(f"Maintenance job ended on {datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")}")
time.sleep(15)
processor.begin_maintenance()
processor.log("-- PORTAL MAINTENANCE JOB COMPLETE")