spaceoddity.py
The main file that runs the program
This file is executable and can be called from the terminal like:
foo@bar:~$ cd [path to directory of this file] foo@bar:~[path to directory of this file] ./spaceoddity.py [cmd line]
or if installed in a global location:
foo@bar:~$ spaceoddity [cmd line]
Typical usage is show in the main() method.
Spaceoddity
Bases: SpaceoddityBase
The main class, responsible for the operation of the program
Public methods
main: The main method of the program
This class does the most of the work of a typical CLI program. It parses command line options, loads/saves config files, and performs the operations required for the program.
Source code in src/spaceoddity.py
class Spaceoddity(SpaceoddityBase):
"""
The main class, responsible for the operation of the program
Public methods:
main: The main method of the program
This class does the most of the work of a typical CLI program. It parses
command line options, loads/saves config files, and performs the operations
required for the program.
"""
# --------------------------------------------------------------------------
# Constants
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# strings
# the url to load json from
S_APOD_URL = (
"https://api.nasa.gov/planetary/apod?"
"api_key=K0sNPQo8Dn9f8kaO35hzs8kUnU9bHwhTtazybTbr"
)
# cmd line options
# enable option strings
S_ARG_ENABLE_OPTION = "--enable"
S_ARG_ENABLE_ACTION = "store_true"
S_ARG_ENABLE_DEST = "ENABLE_DEST"
# I18N: enable mode help
S_ARG_ENABLE_HELP = _("enable the program to run automatically")
# disable option strings
S_ARG_DISABLE_OPTION = "--disable"
S_ARG_DISABLE_ACTION = "store_true"
S_ARG_DISABLE_DEST = "DISABLE_DEST"
# I18N: disable mode help
S_ARG_DISABLE_HELP = _("disable the program from running automatically")
# edit option strings
S_ARG_EDIT_OPTION = "-e"
S_ARG_EDIT_ACTION = "store_true"
S_ARG_EDIT_DEST = "EDIT_DEST"
# I18N: edit config file
S_ARG_EDIT_HELP = _("edit the program's configuration file")
# gui option strings
S_ARG_GUI_OPTION = "-g"
S_ARG_GUI_ACTION = "store_true"
S_ARG_GUI_DEST = "GUI_DEST"
# I18N: gui edit config file
S_ARG_GUI_HELP = _("run gui to edit the program's configuration file")
# edit option strings
S_ARG_LOG_OPTION = "-l"
S_ARG_LOG_ACTION = "store_true"
S_ARG_LOG_DEST = "LOG_DEST"
# I18N: view log file
S_ARG_LOG_HELP = _("view the program's log file")
# messages
# I18N: enable cron job
S_MSG_CRON_ADD = _("Enabling cron job... ")
# I18N: disable cron job
S_MSG_CRON_DEL = _("Disabling cron job... ")
# I18N: update cron job
S_MSG_CRON_UPD = _("Updating cron job... ")
# I18N: get initial apod dict
S_MSG_GET = _("Getting data from server... ")
# I18N: check for media type
S_MSG_MEDIA = _("Checking for media type... ")
# I18N: new download is not image
S_MSG_NOT_IMG = _("The new APOD is not an image")
# I18N: check for same url
S_MSG_URL = _("Checking for same URL... ")
# I18N: no change, exit
S_MSG_SAME_URL = _("The APOD picture has not changed")
# I18N: download succeeded
S_MSG_DL = _("Downloading image... ")
# I18N: convert to png
S_MSG_CONVERT = _("Converting image to png... ")
# I18N: get screen size
S_MSG_SCR_SIZE = _("Getting screen size... ")
# I18N: resize and crop
S_MSG_RESIZE = _("Resizing and cropping image... ")
# I18N: make caption
S_MSG_MAKE_CAP = _("Making caption... ")
# I18N: set image as background
S_MSG_SET = _("Setting image as background... ")
# success/fail for any operation
# I18N: success
S_MSG_DONE = _("Done")
# I18N: fail
S_MSG_FAIL = _("Failed")
# errors
# I18N: env failed or returned empty value(s)
S_ERR_CRON_VAL = _("Could not get env values")
# I18N: failed to open log file
S_ERR_LOG = _("Could not open log file")
# I18N: error on initial get
# NB: param is error msg
S_ERR_GET = _("Could not get data from server: {}")
# I18N: could not download new image
# NB: param is error msg
S_ERR_DL = _("Could not download image: {}")
# I18N: failed to get screen size, fatal error
S_ERR_NO_SCR = _("Could not get screen size")
# I18N: could not set new image
# NB: param is error msg
S_ERR_SET = _("Could not set new image: {}")
# I18N: could not edit config file
S_ERR_EDIT = _("Could not edit configuration file")
# I18N: could not load GUI
S_ERR_GUI = _("Could not load GUI")
# file names
# NB: format param is current ext
S_NAME_DL = "apod.{}"
S_NAME_EXT_PNG = ".png"
S_NAME_PNG = "apod.png"
S_NAME_CAP_PNG = "apod_cap.png"
# commands to set image
# NB: param is file path
S_CMD_SET_LIGHT = (
"gsettings set org.gnome.desktop.background picture-uri file://{}"
)
# NB: param is file path
S_CMD_SET_DARK = (
"gsettings set org.gnome.desktop.background picture-uri-dark file://{}"
)
# commands
S_CMD_EDIT = f"/usr/bin/editor {B.P_CFG_DEF}"
S_CMD_LOG = f"/usr/bin/editor {B.P_LOG_DEF}"
S_CMD_GUI = f"spaceoddity-gui {B.P_CFG_DEF}"
# lists
# acceptable media types
L_MEDIA_TYPES = ["image"]
# dicts
# set default config dict
D_DEFAULT = {
K.S_KEY_CRON: {
K.S_KEY_CRON_ENABLED: True,
K.S_KEY_CRON_INTERVAL: 10,
},
K.S_KEY_CAPTION: {
K.S_KEY_CAPTION_SHOW: True,
K.S_KEY_CAPTION_POS: K.S_KEY_CAP_POS_BR,
K.S_KEY_CAPTION_WRAP: 80,
},
K.S_KEY_INFO: {
K.S_KEY_APOD_TITLE: True,
K.S_KEY_APOD_DATE: True,
K.S_KEY_APOD_COPY: True,
K.S_KEY_APOD_EXP: True,
},
K.S_KEY_FONT: {
K.S_KEY_FONT_NAME: "",
K.S_KEY_FONT_SIZE: 20,
K.S_KEY_FONT_COLOR: [0, 0, 0],
K.S_KEY_FONT_TRANS: 255,
},
K.S_KEY_BOX: {
K.S_KEY_BOX_COLOR: [255, 255, 255],
K.S_KEY_BOX_TRANS: 128,
K.S_KEY_BOX_RAD: 20,
K.S_KEY_BOX_PAD: 10,
},
K.S_KEY_PAD: {
K.S_KEY_PAD_L: 10,
K.S_KEY_PAD_T: 45,
K.S_KEY_PAD_R: 10,
K.S_KEY_PAD_B: 10,
},
K.S_KEY_APOD: {},
K.S_KEY_SCREEN: {
K.S_KEY_SCREEN_WIDTH: 0,
K.S_KEY_SCREEN_HEIGHT: 0,
},
}
# --------------------------------------------------------------------------
# Initialize the new object
# --------------------------------------------------------------------------
def __init__(self):
"""
Initialize the new object
Initializes a new instance of the class, setting the default values
of its properties, and any other code that needs to run to create a
new object.
"""
# do super init
super().__init__()
# set default cfg dict
self._dict_cfg = self.D_DEFAULT.copy()
# paths to images
self._path_img = B.P_DIR_CONF / self.S_NAME_PNG
self._path_img_cap = B.P_DIR_CONF / self.S_NAME_CAP_PNG
# --------------------------------------------------------------------------
# Public methods
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# The main method of the program
# --------------------------------------------------------------------------
def main(self):
"""
The main method of the program
This method is the main entry point for the program, initializing the
program, and performing its steps.
"""
self._logger.info("--------------------------------------------------")
# call boilerplate code
self._setup()
# NB: self._dict_args/self._dict_cfg now available
# sanitize dict_cfg (in situ)
sanitize.do_sanitize(self._dict_cfg)
# ----------------------------------------------------------------------
# check for edit option next
# NB: this will continue when done
arg_edit = self._dict_args[self.S_ARG_EDIT_DEST]
if arg_edit:
self._handle_edit() # ignore all others and continue
# ----------------------------------------------------------------------
# NEXT: check for gui option next
# NB: this will continue when done
# arg_gui = self._dict_args[self.S_ARG_GUI_DEST]
# if arg_gui:
# self._handle_gui(self._arg_debug) # -d and cont if ok/save/apply
# ----------------------------------------------------------------------
# check for log option first
# NB: this will exit when done
arg_log = self._dict_args[self.S_ARG_LOG_DEST]
if arg_log:
self._handle_log() # ignore all others and quit
# ----------------------------------------------------------------------
# handle enable/disable next
# NB: whether you edit the file by hand or not, cmd line overrides it
self._handle_cron() # ignore all others and quit if --disable
# ----------------------------------------------------------------------
# get json from nasa and make a dict
dict_new = self._get_apod_dict()
# check if media type is good and the url has changed
should_dl = self._check_media_type(
dict_new
) and not self._check_same_url(dict_new)
# ----------------------------------------------------------------------
# image is valid type and url has changed
if should_dl:
# apply new dict to config
self._dict_cfg[K.S_KEY_APOD] = dict_new
# download new image
self._get_apod_image()
self._convert_to_png()
self._get_screen_size()
self._resize_and_crop()
# ----------------------------------------------------------------------
# make new caption
# NB: always do this to check for cfg changes
self._do_caption()
# always do this
self._set_image()
# ----------------------------------------------------------------------
# teardown
# call boilerplate code
self._teardown()
# --------------------------------------------------------------------------
# Private methods
# --------------------------------------------------------------------------
# NB: these are the main steps, called in order from main
# --------------------------------------------------------------------------
# Boilerplate to use at the start of main
# --------------------------------------------------------------------------
def _setup(self):
"""
Boilerplate to use at the start of main
Perform some mundane stuff like setting properties.
If you implement this function. make sure to call super() LAST!!!
"""
# ----------------------------------------------------------------------
# get a mutually exclusive group
group = self._parser.add_mutually_exclusive_group()
# add debug option
group.add_argument(
self.S_ARG_ENABLE_OPTION,
action=self.S_ARG_ENABLE_ACTION,
dest=self.S_ARG_ENABLE_DEST,
help=self.S_ARG_ENABLE_HELP,
)
# add debug option
group.add_argument(
self.S_ARG_DISABLE_OPTION,
action=self.S_ARG_DISABLE_ACTION,
dest=self.S_ARG_DISABLE_DEST,
help=self.S_ARG_DISABLE_HELP,
)
# ----------------------------------------------------------------------
# add edit option
self._parser.add_argument(
self.S_ARG_EDIT_OPTION,
action=self.S_ARG_EDIT_ACTION,
dest=self.S_ARG_EDIT_DEST,
help=self.S_ARG_EDIT_HELP,
)
# NEXT: add gui option
# add gui option
# self._parser.add_argument(
# self.S_ARG_GUI_OPTION,
# action=self.S_ARG_GUI_ACTION,
# dest=self.S_ARG_GUI_DEST,
# help=self.S_ARG_GUI_HELP,
# )
# NEXT: move to base (_add_cmd_log)
# add log option
self._parser.add_argument(
self.S_ARG_LOG_OPTION,
action=self.S_ARG_LOG_ACTION,
dest=self.S_ARG_LOG_DEST,
help=self.S_ARG_LOG_HELP,
)
# NEXT: move to base (_add_cmd_debug)
# add debug option (all this stuff is in super)
self._parser.add_argument(
self.S_ARG_DBG_OPTION,
action=self.S_ARG_DBG_ACTION,
dest=self.S_ARG_DBG_DEST,
help=self.S_ARG_DBG_HELP,
)
# NEXT: _add_cmd_debug
# NEXT: _add_cmd_log
# NEXT: _add_cmd_uninst
# do super setup after add custom cmd line opts (this is where parse
# happens)
super()._setup()
# NB: self._dict_args/self._dict_cfg now available
# ----------------------------------------------------------------------
# print stuff
print()
print(self.S_ABOUT)
print()
print(self.S_ABOUT_HELP)
print()
# --------------------------------------------------------------------------
# Handle the edit (-e) cmd option
# --------------------------------------------------------------------------
def _handle_edit(self):
"""
Handle the edit (-e) cmd option
This function offloads handling of the edit command. It opens the
config file in the default CLI editor.
"""
# load cfg file in nano
try:
F.run(self.S_CMD_EDIT)
except F.CNRunError as e:
F.printc(self.S_ERR_EDIT, fg=F.C_FG_WHITE, bg=F.C_BG_RED)
print(e.output)
self._logger.error(self.S_ERR_EDIT)
self._logger.error(e.output)
sys.exit(-1)
# reload config file
self._load_config()
# (re) sanitize dict_cfg after edit
sanitize.do_sanitize(self._dict_cfg)
# --------------------------------------------------------------------------
# Handle the GUI (-g) cmd option
# --------------------------------------------------------------------------
def _handle_gui(self):
"""
Handle the GUI (-g) cmd option
This function offloads handling of the open GUI command. It opens the
GUI application to edit the configuration options.
"""
# load cfg file in gui
try:
F.run(self.S_CMD_GUI)
except F.CNRunError as e:
F.printc(self.S_ERR_GUI, fg=F.C_FG_WHITE, bg=F.C_BG_RED)
print(e.output)
self._logger.error(self.S_ERR_GUI)
self._logger.error(e.output)
sys.exit(-1)
# reload config file
self._load_config()
# (re) sanitize dict_cfg after edit
sanitize.do_sanitize(self._dict_cfg)
# --------------------------------------------------------------------------
# Handle the view log (-l) cmd option
# --------------------------------------------------------------------------
def _handle_log(self):
"""
Handle the view log (-l) cmd option
This function offloads handling of the view log command. It opens the
log file in the default CLI editor.
"""
# load log file in nano
try:
F.run(self.S_CMD_LOG)
except F.CNRunError as e:
F.printc(self.S_ERR_LOG, fg=F.C_FG_WHITE, bg=F.C_BG_RED)
print(e.output)
self._logger.error(self.S_ERR_LOG)
self._logger.error(e.output)
sys.exit(-1)
# --------------------------------------------------------------------------
# Handle cron changes from command line or config file
# --------------------------------------------------------------------------
def _handle_cron(self):
"""
Handle cron changes from command line or config file
This function offloads handling of cron changes from either the command
line or editing the config file manually.
"""
# ----------------------------------------------------------------------
# get cron options from cfg file
dict_cron = self._dict_cfg.get(K.S_KEY_CRON, {})
cfg_enabled = dict_cron.get(
K.S_KEY_CRON_ENABLED,
self.D_DEFAULT[K.S_KEY_CRON][K.S_KEY_CRON_ENABLED],
)
cfg_interval = dict_cron.get(
K.S_KEY_CRON_INTERVAL,
self.D_DEFAULT[K.S_KEY_CRON][K.S_KEY_CRON_INTERVAL],
)
# ----------------------------------------------------------------------
# action based on cmd arg
val_arg_no_chg = 0 # neither
val_arg_enable = 1 # --enable
val_arg_disable = -1 # --disable
# check for cmd line args
val_arg = val_arg_no_chg
arg_enable = self._dict_args.get(self.S_ARG_ENABLE_DEST, False)
if arg_enable:
val_arg = val_arg_enable
arg_disable = self._dict_args.get(self.S_ARG_DISABLE_DEST, False)
if arg_disable:
val_arg = val_arg_disable
# cmd line overrides all, wants to enable
if val_arg == val_arg_enable:
# show some text
print(self.S_MSG_CRON_ADD, end="", flush=True)
log = self.S_MSG_CRON_ADD
# try to add job
try:
cron.add(cfg_interval)
dict_cron[K.S_KEY_CRON_ENABLED] = True
self._save_config()
except (F.CNRunError, OSError) as e:
# show some text
F.printc(self.S_MSG_FAIL, fg=F.C_FG_RED, bold=True)
print(self.S_ERR_CRON_VAL)
self._logger.error(log + self.S_MSG_FAIL)
# NB: OSError raised if no env, returns empty string
if isinstance(e, OSError):
e = self.S_ERR_CRON_VAL
self._logger.error(e)
# could not enable, continue
return
# show some text
F.printc(self.S_MSG_DONE, fg=F.C_FG_GREEN, bold=True)
self._logger.info(log + self.S_MSG_DONE)
# enabled, continue
return
# cmd line overrides all, wants to disable
elif val_arg == val_arg_disable:
# show some text
print(self.S_MSG_CRON_DEL, end="", flush=True)
log = self.S_MSG_CRON_DEL
# remove
cron.remove()
dict_cron[K.S_KEY_CRON_ENABLED] = False
self._save_config()
# show some text
F.printc(self.S_MSG_DONE, fg=F.C_FG_GREEN, bold=True)
self._logger.info(log + self.S_MSG_DONE)
# don't do anything else
sys.exit(0)
# ----------------------------------------------------------------------
# next is to see if file matches current cron
# show some text
print(self.S_MSG_CRON_UPD, end="", flush=True)
log = self.S_MSG_CRON_UPD
try:
cron.update(cfg_enabled, cfg_interval)
except (F.CNRunError, OSError) as e:
# tell the TUI/log
F.printc(self.S_MSG_FAIL, fg=F.C_FG_RED, bold=True)
print(self.S_ERR_CRON_VAL)
self._logger.error(log + self.S_MSG_FAIL)
# NB: OSError raised if no env, returns empty string
if isinstance(e, OSError):
e = self.S_ERR_CRON_VAL
self._logger.error(e)
# could not enable, continue
return
# show some text
F.printc(self.S_MSG_DONE, fg=F.C_FG_GREEN, bold=True)
self._logger.info(log + self.S_MSG_DONE)
# --------------------------------------------------------------------------
# Get json from api.nasa.gov
# --------------------------------------------------------------------------
def _get_apod_dict(self) -> dict[str, str]:
"""
Get the latest NASA dict
:return: The latest NASA dict
:rtype: dict[str, str]
"""
# print some info
print(self.S_MSG_GET, end="", flush=True)
log = self.S_MSG_GET
# default result
dict_new = {}
# get the nasa json
try:
# get json from url
with request.urlopen(self.S_APOD_URL) as response:
response_text = response.read()
# make a dict from json
dict_new = json.loads(response_text)
# problem
except OSError as error:
# prob no internet
F.printc(self.S_MSG_FAIL, fg=F.C_FG_RED, bold=True)
print(self.S_ERR_GET.format(error))
self._logger.error(log + self.S_MSG_FAIL)
self._logger.error(self.S_ERR_GET.format(error))
self._teardown()
sys.exit(-1)
# show some text
F.printc(self.S_MSG_DONE, fg=F.C_FG_GREEN, bold=True)
self._logger.info(log + self.S_MSG_DONE)
# return the result
return dict_new
# --------------------------------------------------------------------------
# Check media type
# --------------------------------------------------------------------------
def _check_media_type(self, dict_new: dict[str, str]) -> bool:
"""
Check media type
:param dict_new: The dictionary to test
:type dict_new: dict
:return: True if the media type is acceptable, else False
:rtype: bool
"""
# sanity check
if len(dict_new) == 0:
return False
# ----------------------------------------------------------------------
# show some text
print(self.S_MSG_MEDIA, end="", flush=True)
log = self.S_MSG_MEDIA
# check if today's apod is an image (sometimes it's a video)
media_type = dict_new[K.S_KEY_APOD_TYPE]
# check if today's apod is an image (sometimes it's a video)
if not media_type in self.L_MEDIA_TYPES:
# not a fatal error
F.printc(self.S_MSG_FAIL, fg=F.C_FG_RED, bold=True)
print(self.S_MSG_NOT_IMG)
self._logger.error(log + self.S_MSG_FAIL)
self._logger.error(self.S_MSG_NOT_IMG)
return False
# show some text
F.printc(self.S_MSG_DONE, fg=F.C_FG_GREEN, bold=True)
self._logger.info(log + self.S_MSG_DONE)
# media type is ok
return True
# --------------------------------------------------------------------------
# Check for same URL
# --------------------------------------------------------------------------
def _check_same_url(self, dict_new: dict[str, str]) -> bool:
"""
Check for same URL
:param dict_new: The dictionary to test
:type dict_new: dict
:return: True if the URL is the same, else False
:rtype: bool
"""
# sanity checks
if len(dict_new) == 0:
return False
if not self._path_img.exists():
return False
# ----------------------------------------------------------------------
# show some text
print(self.S_MSG_URL, end="", flush=True)
log = self.S_MSG_URL
# get current dict
dict_old = self._dict_cfg[K.S_KEY_APOD]
# get old/new URLs
new_hd = dict_new.get(K.S_KEY_APOD_HDURL, "")
old_hd = dict_old.get(K.S_KEY_APOD_HDURL, "")
new_sd = dict_new.get(K.S_KEY_APOD_URL, "")
old_sd = dict_old.get(K.S_KEY_APOD_URL, "")
# return true if same URL
res = old_hd == new_hd or old_sd == new_sd
# show some text
F.printc(self.S_MSG_DONE, fg=F.C_FG_GREEN, bold=True)
self._logger.info(log + self.S_MSG_DONE)
# return result
return res
# --------------------------------------------------------------------------
# Get image from NASA
# --------------------------------------------------------------------------
def _get_apod_image(self):
"""
Get image from NASA
Sets self._path_img to:
B.P_DIR_CONF / "tests/apod.jpg"
or
B.P_DIR_CONF / self.S_NAME_DL
"""
# ----------------------------------------------------------------------
# show some text
print(self.S_MSG_DL, end="", flush=True)
log = self.S_MSG_DL
# ----------------------------------------------------------------------
# NB: already set curr apod dict to new apod dict
# get current apod dict
apod_dict = self._dict_cfg[K.S_KEY_APOD]
# sanity check
if len(apod_dict) == 0:
return False
# get most appropriate URL
src_url = ""
if K.S_KEY_APOD_HDURL in apod_dict:
src_url = apod_dict[K.S_KEY_APOD_HDURL]
elif K.S_KEY_APOD_URL in apod_dict:
src_url = apod_dict[K.S_KEY_APOD_URL]
# get ext of download
url_ext = src_url.split(".")[-1]
# get new pic name
dl_name = self.S_NAME_DL.format(url_ext)
dl_path = B.P_DIR_CONF / dl_name
# try to download image
try:
# download the image
request.urlretrieve(src_url, dl_path)
# set self prop (initial download)
self._path_img = dl_path
except OSError as error:
# this is a fatal error
F.printc(self.S_MSG_FAIL, fg=F.C_FG_RED, bold=True)
print(self.S_ERR_DL.format(error))
self._logger.error(log + self.S_MSG_FAIL)
self._logger.error(self.S_ERR_DL.format(error))
self._teardown()
sys.exit(-1)
# ----------------------------------------------------------------------
# show some text
F.printc(self.S_MSG_DONE, fg=F.C_FG_GREEN, bold=True)
self._logger.info(log + self.S_MSG_DONE)
# --------------------------------------------------------------------------
# Convert image to png if necessary
# --------------------------------------------------------------------------
def _convert_to_png(self):
"""
Convert image to png if necessary
"""
# ----------------------------------------------------------------------
# convert img to png if necessary
path_ext = self._path_img.suffix
if path_ext != self.S_NAME_EXT_PNG:
# show some text
print(self.S_MSG_CONVERT, end="", flush=True)
log = self.S_MSG_CONVERT
# store old name
old_path = self._path_img
# get new pic name
self._path_img = B.P_DIR_CONF / self.S_NAME_PNG
# open image
img_src = Image.open(old_path)
# save image as png
img_src.save(self._path_img)
# delete old image (if it was not png)
old_path.unlink()
# show some text
F.printc(self.S_MSG_DONE, fg=F.C_FG_GREEN, bold=True)
self._logger.info(log + self.S_MSG_DONE)
# --------------------------------------------------------------------------
# Get screen size (only when install/first run)
# --------------------------------------------------------------------------
def _get_screen_size(self):
"""
Get screen size (only when install/first run)
"""
# ----------------------------------------------------------------------
# show some text
print(self.S_MSG_SCR_SIZE, end="", flush=True)
log = self.S_MSG_SCR_SIZE
# check if we already have screen size
dict_screen = self._dict_cfg[K.S_KEY_SCREEN]
scr_w = dict_screen[K.S_KEY_SCREEN_WIDTH]
scr_h = dict_screen[K.S_KEY_SCREEN_HEIGHT]
if scr_w == 0 or scr_h == 0:
try:
image.get_screen_size(dict_screen)
except OSError:
# failed to get screen size, fatal error
F.printc(self.S_MSG_FAIL, fg=F.C_FG_RED, bold=True)
print(self.S_ERR_NO_SCR)
self._logger.error(log + self.S_MSG_FAIL)
self._logger.error(self.S_ERR_NO_SCR)
self._teardown()
sys.exit(-1)
# show some text
F.printc(self.S_MSG_DONE, fg=F.C_FG_GREEN, bold=True)
self._logger.info(log + self.S_MSG_DONE)
# --------------------------------------------------------------------------
# Resize and crop image
# --------------------------------------------------------------------------
def _resize_and_crop(self):
"""
Resize and crop image
"""
# ----------------------------------------------------------------------
# show some text
print(self.S_MSG_RESIZE, end="", flush=True)
log = self.S_MSG_RESIZE
# check if we already have screen size
dict_screen = self._dict_cfg[K.S_KEY_SCREEN]
# resize and crop png
image.resize_and_crop(self._path_img, dict_screen)
# show some text
F.printc(self.S_MSG_DONE, fg=F.C_FG_GREEN, bold=True)
self._logger.info(log + self.S_MSG_DONE)
# --------------------------------------------------------------------------
# Create caption image
# --------------------------------------------------------------------------
def _do_caption(self):
"""
Create caption image
"""
# ----------------------------------------------------------------------
# show some text
print(self.S_MSG_MAKE_CAP, end="", flush=True)
log = self.S_MSG_MAKE_CAP
# ----------------------------------------------------------------------
# do the caption
caption.do_caption(self._path_img, self._path_img_cap, self._dict_cfg)
# ----------------------------------------------------------------------
# show some text
F.printc(self.S_MSG_DONE, fg=F.C_FG_GREEN, bold=True)
self._logger.info(log + self.S_MSG_DONE)
# --------------------------------------------------------------------------
# Set the wallpaper
# --------------------------------------------------------------------------
def _set_image(self):
"""
Set the wallpaper
"""
# ----------------------------------------------------------------------
# show some text
print(self.S_MSG_SET, end="", flush=True)
log = self.S_MSG_SET
# ----------------------------------------------------------------------
# assume no cap
path_img = self._path_img
# or yes cap
if self._dict_cfg[K.S_KEY_CAPTION][K.S_KEY_CAPTION_SHOW]:
path_img = self._path_img_cap
# call cmds to set wallpaper for light/dark (ubuntu only?)
try:
F.run(self.S_CMD_SET_LIGHT.format(path_img))
F.run(self.S_CMD_SET_DARK.format(path_img))
except F.CNRunError as error:
F.printc(self.S_MSG_FAIL, fg=F.C_FG_RED, bold=True)
print(self.S_ERR_SET.format(error))
self._logger.error(log + self.S_MSG_FAIL)
self._logger.error(self.S_ERR_SET.format(error))
self._teardown()
sys.exit(-1)
# show some text
F.printc(self.S_MSG_DONE, fg=F.C_FG_GREEN, bold=True)
self._logger.info(log + self.S_MSG_DONE)
# --------------------------------------------------------------------------
# Boilerplate to use at the end of main
# --------------------------------------------------------------------------
def _teardown(self):
"""
Boilerplate to use at the end of main
Perform some mundane stuff like saving properties.
"""
# just print a blank line before exiting
print()
# do super (save dict)
super()._teardown()
__init__()
Initialize the new object
Initializes a new instance of the class, setting the default values of its properties, and any other code that needs to run to create a new object.
Source code in src/spaceoddity.py
def __init__(self):
"""
Initialize the new object
Initializes a new instance of the class, setting the default values
of its properties, and any other code that needs to run to create a
new object.
"""
# do super init
super().__init__()
# set default cfg dict
self._dict_cfg = self.D_DEFAULT.copy()
# paths to images
self._path_img = B.P_DIR_CONF / self.S_NAME_PNG
self._path_img_cap = B.P_DIR_CONF / self.S_NAME_CAP_PNG
main()
The main method of the program
This method is the main entry point for the program, initializing the program, and performing its steps.
Source code in src/spaceoddity.py
def main(self):
"""
The main method of the program
This method is the main entry point for the program, initializing the
program, and performing its steps.
"""
self._logger.info("--------------------------------------------------")
# call boilerplate code
self._setup()
# NB: self._dict_args/self._dict_cfg now available
# sanitize dict_cfg (in situ)
sanitize.do_sanitize(self._dict_cfg)
# ----------------------------------------------------------------------
# check for edit option next
# NB: this will continue when done
arg_edit = self._dict_args[self.S_ARG_EDIT_DEST]
if arg_edit:
self._handle_edit() # ignore all others and continue
# ----------------------------------------------------------------------
# NEXT: check for gui option next
# NB: this will continue when done
# arg_gui = self._dict_args[self.S_ARG_GUI_DEST]
# if arg_gui:
# self._handle_gui(self._arg_debug) # -d and cont if ok/save/apply
# ----------------------------------------------------------------------
# check for log option first
# NB: this will exit when done
arg_log = self._dict_args[self.S_ARG_LOG_DEST]
if arg_log:
self._handle_log() # ignore all others and quit
# ----------------------------------------------------------------------
# handle enable/disable next
# NB: whether you edit the file by hand or not, cmd line overrides it
self._handle_cron() # ignore all others and quit if --disable
# ----------------------------------------------------------------------
# get json from nasa and make a dict
dict_new = self._get_apod_dict()
# check if media type is good and the url has changed
should_dl = self._check_media_type(
dict_new
) and not self._check_same_url(dict_new)
# ----------------------------------------------------------------------
# image is valid type and url has changed
if should_dl:
# apply new dict to config
self._dict_cfg[K.S_KEY_APOD] = dict_new
# download new image
self._get_apod_image()
self._convert_to_png()
self._get_screen_size()
self._resize_and_crop()
# ----------------------------------------------------------------------
# make new caption
# NB: always do this to check for cfg changes
self._do_caption()
# always do this
self._set_image()
# ----------------------------------------------------------------------
# teardown
# call boilerplate code
self._teardown()