Source code for halo.DataStore

"""
Handles the data storage and retrieval.
"""

import sqlite3
import time
from functools import wraps
from os import path

from halo.settings import DEFAULT_DB_LOCATION, DEFAULT_WEATHER_API_KEY, \
    DEFAULT_BACKGROUND_IMAGE, DEFAULT_SCREEN_HEIGHT, DEFAULT_SCREEN_WIDTH


[docs]def query(fn): """A decorator which ensures if a database lock ever happen, then sqlite query is retired seamlessly.""" @wraps(fn) def wrap(*args, **kwargs): while True: try: fn(*args, **kwargs) break except sqlite3.OperationalError as e: if "locked" in str(e): time.sleep(1) print("DB is in locked state. Retrying...") else: raise return wrap
[docs]class DataStore: """sqlite3 database class that store user data and app settings.""" __SCREEN_HEIGHT = DEFAULT_SCREEN_HEIGHT __SCREEN_WIDTH = DEFAULT_SCREEN_WIDTH __API_KEY = DEFAULT_WEATHER_API_KEY __BG_FILE = DEFAULT_BACKGROUND_IMAGE def __init__(self, db_location=DEFAULT_DB_LOCATION): """ Initialises the connection and create a cursor. :param db_location: File location of database. """ self.__DB_LOCATION = db_location self.__conn = sqlite3.connect(self.__DB_LOCATION) if self.__conn is None: raise sqlite3.DatabaseError("Could not get a database connection") self.__cur = self.__conn.cursor() self._first_run() self.refresh_preference() def __del__(self): self.__conn.close() @query def _first_run(self): """Initially create the tables and row contents.""" self.__cur.execute('''CREATE TABLE IF NOT EXISTS city(city_name text, country_code text, UNIQUE(city_name, country_code) ON CONFLICT REPLACE)''') self.__cur.execute('''CREATE TABLE IF NOT EXISTS setting(name text, value text, UNIQUE(name))''') self.__cur.execute('''INSERT or IGNORE INTO setting VALUES('api-key',?)''', (DEFAULT_WEATHER_API_KEY,)) self.__cur.execute('''INSERT or IGNORE INTO setting VALUES('bg-image',?)''', (DEFAULT_BACKGROUND_IMAGE,)) self.__cur.execute('''INSERT or IGNORE INTO setting VALUES('screen-width',?)''', (DEFAULT_SCREEN_WIDTH,)) self.__cur.execute('''INSERT or IGNORE INTO setting VALUES('screen-height',?)''', (DEFAULT_SCREEN_HEIGHT,)) self.__conn.commit()
[docs] def refresh_preference(self): """Loads latest preference values from db""" DataStore.__API_KEY = self.__fetch_settings('api-key') bg = self.__fetch_settings('bg-image') DataStore.__BG_FILE = bg if path.isfile(bg) else DEFAULT_BACKGROUND_IMAGE DataStore.__SCREEN_WIDTH = self.__fetch_settings('screen-width') DataStore.__SCREEN_HEIGHT = self.__fetch_settings('screen-height')
def __fetch_settings(self, name): """ Fetches the specified settings from database. :param name: Name of preference to be fetched. :return: Returns the value of the preference. """ return self.__cur.execute('''SELECT value FROM setting WHERE "name"=?''', (name,)).fetchone()[0] @query def __update_settings(self, name, value): """ Updates specific preference value. :param name: name of preference to be updated. :param value: value of preference. """ self.__cur.execute('''UPDATE setting SET "value"=? WHERE "name"=?''', (value, name)) self.__conn.commit()
[docs] def get_cities(self): """ Get a list of all the cities :return: list of city name and country codes. """ return list(self.__cur.execute('''SELECT * FROM city'''))
[docs] @query def add_city(self, params): """ Adds the city to db if it doesn't exists. :param params: a tuple of city and country code """ self.__cur.execute('''INSERT INTO city VALUES (?,?)''', params) self.__conn.commit()
[docs] @staticmethod def get_api_key(): """ Retrieves the api key :return: API key """ return DataStore.__API_KEY if len(DataStore.__API_KEY) == 32 else DEFAULT_WEATHER_API_KEY
[docs] def set_api_key(self, key): """ Writes the new API key if it's valid. :param key: API key """ if len(key) == 32: self.__update_settings('api-key', key) else: print("Invalid api key provided. Ignoring silently.")
[docs] @staticmethod def get_bg_file(): """ Retrieves the background image path. :return: image file path """ return DataStore.__BG_FILE if path.isfile(DataStore.__BG_FILE) else DEFAULT_BACKGROUND_IMAGE
[docs] def set_bg_file(self, file_name): """ Writes the new background image path if it exists. :param file_name: file path """ if path.isfile(file_name): self.__update_settings('bg-image', file_name) else: print("Invalid background image file path provided. Ignoring silently.")
[docs] def screen(self, width, height): """ Save the screen width and height to the db. :param width: Width of screen. :param height: Height of screen. """ try: self.__update_settings('screen-width', max(width, DEFAULT_SCREEN_WIDTH)) self.__update_settings('screen-height', max(height, DEFAULT_SCREEN_HEIGHT)) except sqlite3.OperationalError: pass
[docs] @staticmethod def get_width(): """Retrieves the screen width.""" return int(DataStore.__SCREEN_WIDTH)
[docs] @staticmethod def get_height(): """Retrieves the screen height.""" return int(DataStore.__SCREEN_HEIGHT)