1
0

Merge pull request #14 from mbirth/feature/sqlite-support

Feature/sqlite support
This commit is contained in:
tomyvi 2018-06-07 15:44:15 +02:00 committed by GitHub
commit 021fee0911
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 356 additions and 171 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
config.inc.php
owntracks.db3
# Windows image file caches
Thumbs.db

View File

@ -27,19 +27,19 @@ That's it !
1. Download the source code and copy the content of the directory to your prefered location
2. Edit the ```config.inc.sample.php``` file to setup access to your database and rename to ```config.inc.php``` :
```php
$_config['sql_host'] // sql server hostname
$_config['sql_user'] // sql server username
$_config['sql_pass'] // sql server username password
$_config['sql_db'] // database name
$_config['sql_prefix'] // table prefix
$_config['sql_type'] // database type 'mysql' (MySQL/MariaDB) or 'sqlite'
$_config['sql_host'] // sql server hostname (only needed for 'mysql')
$_config['sql_user'] // sql server username (only needed for 'mysql')
$_config['sql_pass'] // sql server username password (only needed for 'mysql')
$_config['sql_db'] // database name or SQLite filename
$_config['sql_prefix'] // table prefix (only needed for 'mysql')
$_config['default_accuracy'] // default maxymum accuracy for location record to be displayed on the map
$_config['enable_geo_reverse'] // set to TRUE to enable geo decoding of location records
$_config['geo_reverse_lookup_url'] // geodecoding api url, will be appended with lat= & lon= attributes
```
3. Create datatable using schema.sql (in the 'sql' directory)
3. Create datatable using schema_mysql.sql or schema_sqlite.sql (in the 'sql' directory)
#### Owntracks app
Follow [Owntracks Booklet](http://owntracks.org/booklet/features/settings/) to setup your Owntracks app :

View File

@ -4,16 +4,22 @@
setlocale(LC_TIME, "fr_FR");
$_config['log_enable'] = True;
// MySQL / MariaDB
$_config['sql_type'] = 'mysql';
$_config['sql_host'] = '';
$_config['sql_user'] = '';
$_config['sql_pass'] = '';
$_config['sql_db'] = '';
$_config['sql_prefix'] = '';
// SQLite
//$_config['sql_type'] = 'sqlite';
//$_config['sql_db'] = 'owntracks.db3';
$_config['default_accuracy'] = 1000; //meters
$_config['log_enable'] = True;
$_config['default_accuracy'] = 1000; //meters
$_config['default_trackerID'] = 'all';
$_config['geo_reverse_lookup_url'] = "http://193.63.75.109/reverse?format=json&zoom=18&accept-language=fr&addressdetails=0&email=sjobs@apple.com&";

85
lib/db/AbstractDb.php Normal file
View File

@ -0,0 +1,85 @@
<?php
class AbstractDb
{
protected $db;
protected $prefix;
protected function execute(string $sql, array $params): bool
{
// Run query without result
}
protected function query(string $sql, array $params): array
{
// Run query and fetch results
}
public function isEpochExisting(string $trackerId, int $epoch): bool
{
$sql = 'SELECT epoch FROM ' . $this->prefix . 'locations WHERE tracker_id = ? AND epoch = ?';
$result = $this->query($sql, array($trackerId, $epoch));
return (count($result) > 0);
}
public function addLocation(
int $accuracy = null,
int $altitude = null,
int $battery_level = null,
int $heading = null,
string $description = null,
string $event = null,
float $latitude,
float $longitude,
int $radius = null,
string $trig = null,
string $tracker_id = null,
int $epoch,
int $vertical_accuracy = null,
int $velocity = null,
float $pressure = null,
string $connection = null,
int $place_id = null,
int $osm_id = null
): bool {
$sql = 'INSERT INTO ' . $this->prefix . 'locations (accuracy, altitude, battery_level, heading, description, event, latitude, longitude, radius, trig, tracker_id, epoch, vertical_accuracy, velocity, pressure, connection, place_id, osm_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
$params = array($accuracy, $altitude, $battery_level, $heading, $description, $event, $latitude, $longitude, $radius, $trig, $tracker_id, $epoch, $vertical_accuracy, $velocity, $pressure, $connection, $place_id, $osm_id);
$result = $this->execute($sql, $params);
return $result;
}
public function getMarkers(int $time_from, int $time_to, int $min_accuracy = 1000): array
{
$sql = 'SELECT * FROM ' . $this->prefix . 'locations WHERE epoch >= ? AND epoch <= ? AND accuracy < ? AND altitude >=0 ORDER BY tracker_id, epoch ASC';
$result = $this->query($sql, array($time_from, $time_to, $min_accuracy));
$markers = array();
foreach ($result as $pr) {
$markers[$pr['tracker_id']][] = $pr;
}
return $markers;
}
public function getMarkerLatLon(int $epoch)
{
$sql = 'SELECT latitude, longitude FROM ' . $this->prefix . 'locations WHERE epoch = ?';
$result = $this->query($sql, array($epoch));
return $result[0];
}
public function deleteMarker(int $epoch)
{
$sql = 'DELETE FROM ' . $this->prefix . 'locations WHERE epoch = ?';
$result = $this->execute($sql, array($epoch));
return $result;
}
public function updateLocationData(int $epoch, float $latitude, float $longitude, string $location_name, int $place_id, int $osm_id)
{
$sql = 'UPDATE ' . $this->prefix . 'locations SET display_name = ?, place_id = ?, osm_id = ? WHERE epoch = ?';
$params = array($location_name, $place_id, $osm_id, $epoch);
$result = $this->execute($sql, $params);
return $result;
}
}

74
lib/db/MySql.php Normal file
View File

@ -0,0 +1,74 @@
<?php
require_once(__DIR__ . '/AbstractDb.php');
class MySql extends AbstractDb
{
public function __construct(string $db, string $hostname = null, string $username = null, string $password = null, string $prefix = '')
{
$this->db = new \mysqli($hostname, $username, $password, $db);
$this->prefix = $prefix;
}
private function prepareStmt(string $sql, array $params)
{
$stmt = $this->db->prepare($sql);
if (!$stmt) {
return false;
}
$typestr = '';
foreach ($params as $p) {
$type = gettype($p);
switch ($type) {
case 'integer':
$typestr .= 'i';
break;
case 'double':
case 'float':
$typestr .= 'd';
break;
default:
case 'string':
$typestr .= 's';
break;
}
}
// Splat operator needs PHP 5.6+
$stmt->bind_param($typestr, ...$params);
return $stmt;
}
protected function query(string $sql, array $params): array
{
$stmt = $this->prepareStmt($sql, $params);
if (!$stmt) {
return false;
}
if (!$stmt->execute()) {
return false;
}
$dbresult = $stmt->get_result();
$result = array();
while ($data = $dbresult->fetch_assoc()) {
// Loop through results here $data[]
$result[] = $data;
}
$stmt->close();
return $result;
}
protected function execute(string $sql, array $params): bool
{
$stmt = $this->prepareStmt($sql, $params);
if (!$stmt) {
return false;
}
$result = $stmt->execute();
if ($result) {
$stmt->close();
}
return $result;
}
}

43
lib/db/SQLite.php Normal file
View File

@ -0,0 +1,43 @@
<?php
require_once(__DIR__ . '/AbstractDb.php');
class SQLite extends AbstractDb
{
public function __construct($db, $hostname = null, $username = null, $password = null, $prefix = '')
{
$this->db = new \PDO('sqlite:' . $db);
$this->prefix = '';
}
protected function query(string $sql, array $params): array
{
$stmt = $this->db->prepare($sql);
if (!$stmt) {
return false;
}
$stmt->execute($params);
$result = array();
while ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) {
// Loop through results here $data[]
$result[] = $data;
}
$stmt->closeCursor();
return $result;
}
protected function execute(string $sql, array $params): bool
{
$stmt = $this->db->prepare($sql);
if (!$stmt) {
return false;
}
$result = $stmt->execute($params);
if ($result) {
$stmt->closeCursor();
}
return $result;
}
}

View File

@ -27,10 +27,21 @@ $payload = file_get_contents("php://input");
_log("Payload = ".$payload);
$data = @json_decode($payload, true);
$response_msg = null;
if ($data['_type'] == 'location') {
# CREATE TABLE locations (dt TIMESTAMP, tid CHAR(2), lat DECIMAL(9,6), lon DECIMAL(9,6));
$mysqli = new mysqli($_config['sql_host'], $_config['sql_user'], $_config['sql_pass'], $_config['sql_db']);
if ($_config['sql_type'] == 'mysql') {
require_once('lib/db/MySql.php');
$sql = new MySql($_config['sql_db'], $_config['sql_host'], $_config['sql_user'], $_config['sql_pass'], $_config['sql_prefix']);
} elseif ($_config['sql_type'] == 'sqlite') {
require_once('lib/db/SQLite.php');
$sql = new SQLite($_config['sql_db']);
} else {
die('Invalid database type: ' . $_config['sql_type']);
}
# CREATE TABLE locations (dt TIMESTAMP, tid CHAR(2), lat DECIMAL(9,6), lon DECIMAL(9,6));
//http://owntracks.org/booklet/tech/json/
//iiiissddissiiidsiis
@ -52,64 +63,60 @@ if ($data['_type'] == 'location') {
if (array_key_exists('conn', $data)) $connection = strval($data['conn']);
$sql = "SELECT epoch FROM ".$_config['sql_prefix']."locations WHERE tracker_id = '$tracker_id' AND epoch = $epoch";
_log("Duplicate SQL = ".$sql);
if ($stmt = $mysqli->prepare($sql)){
$stmt->execute();
$stmt->store_result();
_log("Duplicate SQL : Rows found = ".$stmt->num_rows);
//record only if same data found at same epoch / tracker_id
if (!$sql->isEpochExisting($tracker_id, $epoch)) {
//record only if same data found at same epoch / tracker_id
if($stmt->num_rows == 0) {
$sql = "INSERT INTO ".$_config['sql_prefix']."locations (accuracy, altitude, battery_level, heading, description, event, latitude, longitude, radius, trig, tracker_id, epoch, vertical_accuracy, velocity, pressure, connection, place_id, osm_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('iiiissddissiiidsii', $accuracy, $altitude, $battery_level, $heading, $description, $event, $latitude, $longitude, $radius, $trig, $tracker_id, $epoch, $vertical_accuracy, $velocity, $pressure, $connection, $place_id, $osm_id);
if ($stmt->execute()){
# bind parameters (s = string, i = integer, d = double, b = blob)
http_response_code(200);
$response['msg'] = "OK record saved";
_log("Insert OK");
$result = $sql->addLocation(
$accuracy,
$altitude,
$battery_level,
$heading,
$description,
$event,
$latitude,
$longitude,
$radius,
$trig,
$tracker_id,
$epoch,
$vertical_accuracy,
$velocity,
$pressure,
$connection,
$place_id,
$osm_id
);
}else{
http_response_code(500);
die("Can't write to database : ".$stmt->error);
$response['msg'] = "Can't write to database";
_log("Insert KO - Can't write to database : ".$stmt->error);
}
if ($result) {
http_response_code(200);
_log("Insert OK");
} else {
http_response_code(500);
$response_msg = 'Can\'t write to database';
_log("Insert KO - Can't write to database.");
}
}else{
_log("Duplicate location found for epoc $epoch / tid '$tracker_id' - no insert");
}
$stmt->close();
}else{
http_response_code(500);
die("Can't read from database");
$response['msg'] = "Can't read from database";
_log("Can't read from database");
} else {
_log("Duplicate location found for epoc $epoch / tid '$tracker_id' - no insert");
$response_msg = 'Duplicate location found for epoch. Ignoring.';
}
}else{
} else {
http_response_code(204);
$response['msg'] = "OK type is not location";
_log("OK type is not location : " . $data['_type']);
}
$response = array();
if (!is_null($response_msg)) {
// Add status message to return object (to be shown in app)
$response[] = array(
'_type' => 'cmd',
'action' => 'action',
'content' => $response_msg,
);
}
print json_encode($response);
fclose($fp);
?>

167
rpc.php
View File

@ -8,24 +8,33 @@
$response = array();
$mysqli = new mysqli($_config['sql_host'], $_config['sql_user'], $_config['sql_pass'], $_config['sql_db']);
if ($_config['sql_type'] == 'mysql') {
require_once('lib/db/MySql.php');
/** @var MySql $sql */
$sql = new MySql($_config['sql_db'], $_config['sql_host'], $_config['sql_user'], $_config['sql_pass'], $_config['sql_prefix']);
} elseif ($_config['sql_type'] == 'sqlite') {
require_once('lib/db/SQLite.php');
/** @var SQLite $sql */
$sql = new SQLite($_config['sql_db']);
} else {
die('Invalid database type: ' . $_config['sql_type']);
}
if (array_key_exists('action', $_REQUEST)) {
if($_REQUEST['action'] === 'getMarkers'){
if ($_REQUEST['action'] === 'getMarkers') {
if(!array_key_exists('dateFrom', $_GET)){
if (!array_key_exists('dateFrom', $_GET)) {
$_GET['dateFrom'] = date("Y-m-d");
}
if(!array_key_exists('dateTo', $_GET)){
if (!array_key_exists('dateTo', $_GET)) {
$_GET['dateTo'] = date("Y-m-d");
}
if(array_key_exists('accuracy', $_GET) && $_GET['accuracy'] > 0){
if (array_key_exists('accuracy', $_GET) && $_GET['accuracy'] > 0) {
$accuracy = intVal($_GET['accuracy']);
}else{
} else {
$accuracy = $_config['default_accuracy'];
}
@ -37,155 +46,91 @@
$time_to = mktime(23, 59, 59, $time_to['tm_mon']+1, $time_to['tm_mday'], $time_to['tm_year']+1900);
//$time_to = strtotime('+1 day', $time_to);
$sql = "SELECT * FROM ".$_config['sql_prefix']."locations WHERE epoch >= $time_from AND epoch <= $time_to AND accuracy < ".$accuracy." AND altitude >=0 ORDER BY tracker_id, epoch ASC";
$stmt = $mysqli->prepare($sql);
if(!$stmt){
$response['status'] = false;
$response['error'] = $mysqli->error;
http_response_code(500);
}
$stmt->execute();
$result = $stmt->get_result();
$stmt->store_result();
$tracker_id = "";
while($data = $result->fetch_assoc()){
//Loop through results here $data[]
$markers[$data['tracker_id']][] = $data;
}
$stmt->close();
$response['status'] = true;
$response['markers'] = json_encode($markers);
$markers = $sql->getMarkers($time_from, $time_to, $accuracy);
if ($markers === false) {
$response['status'] = false;
$response['error'] = 'Database query error';
http_response_code(500);
} else {
$response['status'] = true;
$response['markers'] = json_encode($markers);
}
} elseif ($_REQUEST['action'] === 'deleteMarker') {
}else if($_REQUEST['action'] === 'deleteMarker'){
if(!array_key_exists('epoch', $_REQUEST)){
if (!array_key_exists('epoch', $_REQUEST)) {
$response['error'] = "No epoch provided for marker removal";
$response['status'] = false;
http_response_code(204);
}else{
$stmt = $mysqli->prepare("DELETE FROM ".$_config['sql_prefix']."locations WHERE epoch = ?");
if(!$stmt){
$response['error'] = "Unable to prepare statement : " . $mysqli->error;
$result = $sql->deleteMarker($_REQUEST['epoch']);
if ($result === false) {
$response['error'] = 'Unable to delete marker from database.';
$response['status'] = false;
http_response_code(500);
}else{
$stmt->bind_param('i', $_REQUEST['epoch']);
//$stmt->bindParam(':epoc', $_POST['epoch'], PDO::PARAM_INT);
if(!$stmt->execute()){
$response['error'] = "Unable to delete marker from database : " . $stmt->error;
$response['status'] = false;
http_response_code(500);
}
} else {
$response['msg'] = "Marker deleted from database";
$response['status'] = true;
$stmt->close();
}
}
}
}
}else if($_REQUEST['action'] === 'geoDecode'){
} elseif ($_REQUEST['action'] === 'geoDecode') {
if(!array_key_exists('epoch', $_REQUEST)){
if (!array_key_exists('epoch', $_REQUEST)) {
$response['error'] = "No epoch provided for marker removal";
$response['status'] = false;
http_response_code(204);
}else{
} else {
// GET MARKER'S LAT & LONG DATA
$stmt = $mysqli->prepare("SELECT latitude, longitude FROM ".$_config['sql_prefix']."locations WHERE epoch = ?");
if(!$stmt){
$response['error'] = "Unable to prepare statement : " . $mysqli->error;
// GET MARKER'S LAT & LONG DATA
$marker = $sql->getMarkerLatLon($_REQUEST['epoch']);
if ($marker === false) {
$response['error'] = 'Unable to get marker from database.';
$response['status'] = false;
http_response_code(500);
}else{
$stmt->bind_param('i', $_REQUEST['epoch']);
//$stmt->bindParam(':epoc', $_POST['epoch'], PDO::PARAM_INT);
if(!$stmt->execute()){
$response['error'] = "Unable to get marker from database : " . $stmt->error;
$response['status'] = false;
}
$stmt->execute();
$result = $stmt->get_result();
$stmt->store_result();
while($data = $result->fetch_assoc()){
//Loop through results here $data[]
$marker = $data;
}
} else {
$latitude = $marker['latitude'];
$longitude = $marker['longitude'];
// GEO DECODE LAT & LONG
$geo_decode_url = $_config['geo_reverse_lookup_url'] . 'lat=' .$latitude. '&lon='.$longitude;
$geo_decode_json = file_get_contents($geo_decode_url);
$geo_decode_json = file_get_contents($geo_decode_url);
$geo_decode = @json_decode($geo_decode_json, true);
$place_id = intval($geo_decode['place_id']);
$osm_id = intval($geo_decode['osm_id']);
$location = strval($geo_decode['display_name']);
if($location == '') { $location = @json_encode($geo_decode); }
if ($location == '') { $location = @json_encode($geo_decode); }
//UPDATE MARKER WITH GEODECODED LOCATION
$stmt = $mysqli->prepare("UPDATE ".$_config['sql_prefix']."locations SET display_name = ?, place_id = ?, osm_id = ? WHERE epoch = ? AND latitude = ? AND longitude = ?");
$result = $sql->updateLocationData((int)$_REQUEST['epoch'], (float)$latitude, (float)$longitude, $location, $place_id, $osm_id);
if(!$stmt){
$response['error'] = "Unable to prepare statement : " . $mysqli->error;
if ($result === false) {
$response['error'] = 'Unable to update marker in database.';
$response['status'] = false;
http_response_code(500);
}else{
$stmt->bind_param('siiidd', $location, $place_id, $osm_id, $_REQUEST['epoch'], $latitude, $longitude);
if(!$stmt->execute()){
$response['error'] = "Unable to update marker in database : " . $stmt->error;
$response['status'] = false;
http_response_code(500);
}else{
//SEND BACK DATA
$response['msg'] = "Marker's location fetched and saved to database";
$response['location'] = $location;
$response['status'] = true;
}
} else {
$response['msg'] = 'Marker\'s location fetched and saved to database';
$response['location'] = $location;
$response['status'] = true;
}
$stmt->close();
}
}
}else{
} else {
$response['error'] = "No action to perform";
$response['status'] = false;
http_response_code(404);
}
}else{
} else {
$response['error'] = "Invalid request type or no action";
$response['status'] = false;
http_response_code(404);
}
echo json_encode($response);
?>
echo json_encode($response);

24
sql/schema_sqlite.sql Normal file
View File

@ -0,0 +1,24 @@
PRAGMA journal_mode=WAL;
CREATE TABLE "locations" (
"dt" INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,
"accuracy" INTEGER,
"altitude" INTEGER,
"battery_level" INTEGER,
"heading" INTEGER,
"description" TEXT,
"event" TEXT,
"latitude" REAL,
"longitude" REAL,
"radius" INTEGER,
"trig" INTEGER,
"tracker_id" TEXT,
"epoch" INTEGER,
"vertical_accuracy" INTEGER,
"velocity" INTEGER,
"pressure" REAL,
"connection" TEXT,
"place_id" INTEGER,
"osm_id" INTEGER,
"display_name" TEXT
);