map-dots-fetch/map-dots-fetch.py

178 lines
6.6 KiB
Python

### map-dots-fetch
### Connects to a map-dots instance and fetches images, saving them to the filesystem. Cross platform support is
### provided and only the Python standard library is used - it should run on any system with Python 3.x installed
### The script is designed to be run on a schedule using systemd-timers of cron. The choice is yours - of course
### you can also run it manually if you wish.
### A configuration file should be created at /etc/map-dots-fetch/conf.toml or ~/.config/map-dots-fetch/conf.toml for Linux
### or at \Users\{username}\map-dots-fetch.toml for Windows.
### If no configuration file can be found at these paths, the program directory will be checked.
### If both paths exist on Linux, the file in your home directory will take precedence
### Configurable Options (Example configuration):
"""
conf.toml
---------
[server]
# Include the scheme in the url, eg. "https://" or "http://"
url = ""
port = 443
[images]
# Image settings are arrays, this enables you to generate multiple images with
# different settings at the same time.
# Traccar Device ID
deviceId = [1]
# Output Image Size
size = [[1920,1080]]
# Valid values from from & to are: "now", "-hour", "-day", "-week", "-month", "-quarter"
from = ["-month"]
to = ["now"]
# Output Image Style
style = ["circles"]
format = "png"
[files]
destDir = "/var/wallpapers/map-dots"
"""
##### LICENSE #####
"""
Copyright (c) 2023 Frederick Boniface All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
"""
import os, sys, requests, datetime, tomllib
from pathlib import Path
pre_start = datetime.datetime.now().timestamp()
print("Finding configuration file")
user = os.environ.get('USERNAME', os.environ.get('USER'))
os_type = os.name
global_conf_path = "/etc/map-dots-fetch/conf.toml"
home_path = Path.home()
local_conf_path = f"{home_path}/.config/map-dots-fetch/conf.toml"
win_conf_path = f"{home_path}\\map-dots-fetch.toml"
if os.path.exists(local_conf_path) and os_type == "posix":
conf_path = local_conf_path
elif os.path.exists(global_conf_path) and os_type == "posix":
conf_path = global_conf_path
elif os.path.exists(win_conf_path) and os_type == "nt":
conf_path = win_conf_path
elif os.path.exists("conf.toml"):
conf_path = "conf.toml"
else:
if os_type == "posix":
print(f"Unable to find config file after searching '{global_conf_path}', '{local_conf_path}', and './conf.toml'")
elif os_type == "nt":
print(f"Unable to find config file after searching '{win_conf_path}' and this directory")
else:
print(f"System type: {os_type}, not supported.")
sys.exit(1)
print(f"Loading configuration file: {conf_path}")
with open(conf_path, 'rb') as conf:
conf_values = tomllib.load(conf)
request_url = f"{conf_values['server']['url']}:{conf_values['server']['port']}/traccar/"
# Define usable datetime objects
dateMap = {
'now': datetime.datetime.utcnow(),
'-hour': datetime.datetime.utcnow() - datetime.timedelta(hours=1),
'-day': datetime.datetime.utcnow() - datetime.timedelta(days=1),
'-week': datetime.datetime.utcnow() - datetime.timedelta(weeks=1),
'-month': datetime.datetime.utcnow() - datetime.timedelta(days=30), # Approximate 30 days
'-quarter': datetime.datetime.utcnow() - datetime.timedelta(days=90) # Approximate 90 days
}
print("Ensuring destination folders exist")
os.makedirs(conf_values['files']['destDir'], exist_ok=True)
for i in range(len(conf_values['images']['deviceId'])):
img_dev_id = conf_values['images']['deviceId'][i]
img_width, img_height = conf_values['images']['size'][i]
img_from = dateMap[conf_values['images']['from'][i]].strftime("%Y-%m-%dT%H:%M:%SZ")
img_to = dateMap[conf_values['images']['to'][i]].strftime("%Y-%m-%dT%H:%M:%SZ")
img_style = conf_values['images']['style'][i]
img_format = conf_values['images']['format']
print("Requesting image:")
print(f"ID: {img_dev_id}, Size: {img_width}x{img_height}, From/To: {img_from}, {img_to}, Style: {img_style}, Fmt: {img_format}")
params = {
"id": img_dev_id,
"width": img_width,
"height": img_height,
"from": img_from,
"to": img_to,
"format": img_format
}
pre_req_time = datetime.datetime.now().timestamp()
try:
res = requests.get(request_url, params=params, timeout=120)
except requests.Timeout:
print("HTTP request timed out after 2 minutes")
sys.exit(1)
except requests.RequestException as e:
print("Error with HTTP request: ", e)
sys.exit(1)
post_res_time = datetime.datetime.now().timestamp()
print(f"HTTP request completed in {post_res_time - pre_req_time} seconds")
if res.status_code != 200:
print(f"HTTP Response error: {res.status_code}")
sys.exit(1)
print("Ensuring image directory exists")
image_dir = os.path.join(conf_values['files']['destDir'], f"{i}")
os.makedirs(image_dir, exist_ok=True)
print("Deleting old image files")
for filename in os.listdir(image_dir):
file_path = os.path.join(image_dir, filename)
try:
if os.path.isfile(file_path):
os.unlink(file_path)
except Exception as e:
print("Error deleting old file: ", e)
print("Saving image")
image_filename = f"mapdot-{i}-{img_to}.png"
image_path = os.path.join(image_dir, image_filename)
try:
with open(image_path, "wb") as image_file:
image_file.write(res.content)
print(f"Image saved: {image_path}")
except Exception as e:
print(f"Unable to save image file: {e}")
pre_finish = datetime.datetime.now().timestamp()
print(f"map-dots-fetch completed in: {pre_finish - pre_start} seconds")