From 7c690631064d97d7e5f63eccb8a741836592500b Mon Sep 17 00:00:00 2001 From: Aleksa Ognjanovic Date: Sun, 5 Apr 2020 17:09:15 +0200 Subject: [PATCH] Fixed entertainment dependency, started working on settings --- PiTV/application.glade | 66 ++++++++++++++++++++++-- PiTV/application.py | 78 +++++++++++++++------------- PiTV/location.py | 42 +++++++++++++++ PiTV/settings.py | 89 ++++++++++++++++++++++++++++++++ PiTV/settings_view.glade | 69 +++++++++++++++++++++++++ PiTV/settings_view.py | 109 +++++++++++++++++++++++++++++++++++++++ PiTV/sidebar.py | 4 +- PiTV/weather.py | 31 +++++++++++ README.md | 1 - bla.json | 75 --------------------------- requirements.txt | 3 +- 11 files changed, 446 insertions(+), 121 deletions(-) create mode 100644 PiTV/location.py create mode 100644 PiTV/settings.py create mode 100644 PiTV/settings_view.glade create mode 100644 PiTV/settings_view.py create mode 100644 PiTV/weather.py delete mode 100644 bla.json diff --git a/PiTV/application.glade b/PiTV/application.glade index 11e521b..84a8e7b 100644 --- a/PiTV/application.glade +++ b/PiTV/application.glade @@ -324,8 +324,7 @@ Author: GrbavaCigla - - main_divider + True False 6 @@ -333,12 +332,12 @@ Author: GrbavaCigla - + True False slide-up-down - + True True never @@ -383,14 +382,71 @@ Author: GrbavaCigla 1 + + + True + False + + + + + + + + + page2 + page2 + 2 + + True True - 2 + 1 + + + + + + + True + False + 6 + + + + + + True + False + slide-up-down + + + True + True + + + True + False + none + + + + + + + + page0 + page0 + + True + True + 1 + diff --git a/PiTV/application.py b/PiTV/application.py index 9fc0e3d..258aef9 100644 --- a/PiTV/application.py +++ b/PiTV/application.py @@ -4,19 +4,19 @@ from threading import Thread from screeninfo import get_monitors import requests -from entertainment import Weather, Location +from weather import Weather +from location import Location from utils import check_internet from sidebar import SideBar, ListTile, WeatherBox from category import Category - # Bypass linters if True: import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk, GLib, Gio - +HOST = "http://127.0.0.1" HOME_DIR = os.path.dirname(os.path.abspath(__file__)) MONITOR_WIDTH = get_monitors()[0].width @@ -32,7 +32,7 @@ # "TV Shows", # "TV", # "Songs" - # "Settings" + "Settings" ] SIDEBAR_ICONS = [ @@ -41,7 +41,7 @@ # "TV Shows", # "TV", # "Songs" - # "Settings" + "open-menu" ] @@ -69,8 +69,11 @@ def __init__(self): # Website host URL (As I currenly don't have website, # localhost is development sollution) - # Switched to heroku basic plan (free) - self.host = "https://pitv.herokuapp.com" + # Switched to heroku free plan + # Switched to Azure donated by Maker NS + self.host = HOST + + self.fetch_weather = True self.login_init() # Switch this back after debugging @@ -79,17 +82,22 @@ def __init__(self): def home_refresh(self): # Fetch weather info # TODO:Prompt user for his openweathermap api key - if not self.weather_info: - self.weather_info = Weather( - self.openweather_apikey, - self.unit_system, - self.city_and_country - ) - self.weather_box.set_weather_object(self.weather_info, update=True) - else: - self.weather_box.update_data() - self.weather_box.refresh() + if self.fetch_weather: + if not self.weather_info: + self.weather_info = Weather( + self.openweather_apikey, + self.unit_system, + self.city_and_country + ) + self.weather_box.set_weather_object( + self.weather_info, + update=True + ) + + else: + self.weather_box.update_data() + self.weather_box.refresh() GLib.timeout_add( REFRESH_MILLS, @@ -99,30 +107,37 @@ def home_refresh(self): def recheck_network(self): self.network_state = check_internet() - GLib.idle_add(self.current_thread.join) def home_init(self): # Setting objects public variables to their object - self.main_divider = self.builder.get_object("main_divider") - self.main_stack = self.builder.get_object("main_stack") + self.home_divider = self.builder.get_object("home_divider") + self.home_stack = self.builder.get_object("home_stack") self.category_view = self.builder.get_object("category_view") - self.home_scroll_view = self.builder.get_object("home_scroll_view") + self.home_trending_scroll_view = self.builder.get_object( + "home_trending_scroll_view") # Scrolling adjustment self.category_view.set_focus_vadjustment( - self.home_scroll_view.get_vadjustment() + self.home_trending_scroll_view.get_vadjustment() ) # Add Sidebar to window and place it in the beginning - self.sidebar = SideBar(self.main_stack) - self.main_divider.pack_start(self.sidebar, False, False, 0) - self.main_divider.reorder_child(self.sidebar, 0) + self.sidebar = SideBar(self.home_stack) + self.home_divider.pack_start(self.sidebar, False, False, 0) + self.home_divider.reorder_child(self.sidebar, 0) # Fetch some environment variables self.openweather_apikey = os.environ.get("OPEN_WEATHER_API_KEY") self.unit_system = os.environ.get("UNIT_SYSTEM") + # Check if they are empty + if not self.unit_system: + self.unit_system = "metric" + + if not self.openweather_apikey: + self.fetch_weather = False + # Setting weather_info to None self.weather_info = None @@ -146,7 +161,7 @@ def home_init(self): # Make formatted location for open weather map self.city_and_country = "{},{}".format( self.location_info.city, - self.location_info.countryCode + self.location_info.country_code ) # Fetch all data that needs to be refreshed every 2 minutes @@ -286,17 +301,6 @@ def validate_signup(self): self.signup_error_label.set_text( "Error code:" + str(response.status_code)) - # def on_sidebar_row_selected(self, listbox, listbox_row): - # index = listbox_row.get_index() - # item = SIDEBAR_LABELS[index] - # getattr(self, item.lower()+"_stack")() - - # def home_stack(self): - # print("Home") - - # def movies_stack(self): - # print("Movies") - if __name__ == "__main__": app = PiTV() diff --git a/PiTV/location.py b/PiTV/location.py new file mode 100644 index 0000000..6cccd94 --- /dev/null +++ b/PiTV/location.py @@ -0,0 +1,42 @@ +from requests import get +from json import loads +import socket + + +# Returns hostname of machine +def getHostname(): + return socket.getfqdn() + + +# Returns local IP address +def getLocalIP(): + return socket.gethostbyname(socket.gethostname()) + + +class Location: + def __init__(self): + self.location_info = loads(get("http://ip-api.com/json/").text) + + @property + def city(self): + return self.location_info["city"] + + @property + def public_ip(self): + return self.location_info["query"] + + @property + def timezone(self): + return self.location_info["timezone"] + + @property + def country(self): + return self.location_info["country"] + + @property + def country_code(self): + return self.location_info["countryCode"] + + @property + def region(self): + return self.location_info["regionName"] diff --git a/PiTV/settings.py b/PiTV/settings.py new file mode 100644 index 0000000..3fd17bd --- /dev/null +++ b/PiTV/settings.py @@ -0,0 +1,89 @@ +import sqlite3 + +CREATE_TABLE = \ + "CREATE TABLE IF NOT EXISTS users ("\ + "id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, "\ + "username TEXT NOT NULL UNIQUE);" + + +TYPES = { + "int": "INTEGER", + "str": "TEXT", + "bool": "BOOL", + "datetime": "DATE", + "float": "REAL", + "Percentage": "REAL" +} + +SET = "" + +TABLES = "PRAGMA table_info(users);" + + +class UserSettings(sqlite3.Connection): + """Saves user settings to database + + :param username: username + :param location: full path to save the database + + """ + + def __init__(self, username, location, *args): + super().__init__(location, *args) + self.username = username + self.cursor = self.cursor() + self.cursor.execute(CREATE_TABLE) + + self.cursor.execute( + "SELECT username FROM users WHERE username=?", + (username,) + ) + + if not self.cursor.fetchone(): + self.cursor.execute( + "INSERT INTO users (username) VALUES (?)", + (username,) + ) + + def _get_type(self, value): + """ + + :param value: value + :returns: SQLite3 type equivalent + + """ + val_type = type(value).__name__ + return TYPES[val_type] if val_type in TYPES.keys() else "BLOB" + + def set(self, col, value): + """Sets a value for setting + + :param col: setting + :param value: value for setting + + """ + columns = [i[1] for i in self.cursor.execute(TABLES)] + + if not col in columns: + self.cursor.execute( + f"ALTER TABLE users ADD COLUMN {col} {self._get_type(value)}" + ) + + self.cursor.execute( + f"UPDATE users SET {col}=? WHERE username=?", + (value, self.username) + ) + + def setsave(self, col, value): + """Sets a value for setting + + :param col: setting + :param value: value for setting + + """ + self.set(col, value) + self.save() + + def save(self): + """Saves changes to the database""" + self.commit() diff --git a/PiTV/settings_view.glade b/PiTV/settings_view.glade new file mode 100644 index 0000000..10c19cd --- /dev/null +++ b/PiTV/settings_view.glade @@ -0,0 +1,69 @@ + + + + + + diff --git a/PiTV/settings_view.py b/PiTV/settings_view.py new file mode 100644 index 0000000..1a14d83 --- /dev/null +++ b/PiTV/settings_view.py @@ -0,0 +1,109 @@ +""" +SettingsView, SettingsRow template for PiTV, depends on settings_view.glade +TODO: Make this file better coded, add comments, fix issues +This file is the worst +""" +import os +from settings import UserSettings +from pathlib import Path + +# Bypass linters +if True: + import gi + gi.require_version("Gtk", "3.0") + from gi.repository import Gtk + +HOME_DIR = os.path.dirname(os.path.abspath(__file__)) + +WIDGET_GETTERS = { + Gtk.Scale: "get_value", + # Gtk.ComboBox, +} + + +Percentage = float + + +class Setting: + name = None # REQUIRED + name_db = None # IF NULL, SET TO NAME + default = None # IF NULL, SET TO VALUE + available = None + + def __init__(self, name_db, default, name=None, available=None): + self.name_db = name_db + self.available = available + self.name = name if name else name_db + self.default = default + + +@Gtk.Template(filename=os.path.join(HOME_DIR, "settings_view.glade")) +class SettingsView(Gtk.Box): + __gtype_name__ = 'SettingsView' + + def __init__(self, username, settings, **kwargs): + super().__init__(**kwargs) + + # TODO: also find better solution for this + self.settings_list, self.buttons = self.get_children() + self.apply_button, self.reset_button = self.buttons.get_children() + + db_location = os.path.join(str(Path.home()), "pitv.db") + + self.settings_db = UserSettings(username, db_location) + self.settings = settings + self.count = 0 + + self._values = [] + + for i in self.settings: + self.add_setting(i) + + self.apply_button.connect("clicked", self.apply) + self.reset_button.connect("clicked", self.reset) + + def apply(self, button): + # Apply code + # This will save settings to config directory + pass + + def reset(self, button): + # Reset everything + pass + + def add_setting(self, setting): + setter_widget = Gtk.Label(label=str(setting.default)) + + if setting.available: + available_store = Gtk.ListStore(type(setting.default)) + setter_widget = Gtk.ComboBox.new_with_model(available_store) + + for i, val in enumerate(setting.available): + available_store.append([val]) + if val == setting.default: + setter_widget.set_active(i) + + renderer_text = Gtk.CellRendererText() + setter_widget.pack_start(renderer_text, True) + setter_widget.add_attribute(renderer_text, "text", 0) + + print(setter_widget.get_active()) + + elif isinstance(setting.default, Percentage): + setter_widget = Gtk.Scale.new_with_range( + Gtk.Orientation.HORIZONTAL, 0, 100, 1 + ) + setter_widget.set_size_request(500, -1) + + self._values.append(setter_widget) + widget = Gtk.Box() + widget.pack_start(Gtk.Label(label=setting.name), False, False, 0) + widget.pack_end(setter_widget, False, False, 0) + self.settings_list.insert(widget, self.count) + self.count += 1 + + def get_value(self, widget): + pass + + def get_values(self): + return [self.get_value(i) for i in self._values] diff --git a/PiTV/sidebar.py b/PiTV/sidebar.py index ac1a27d..80c6f86 100644 --- a/PiTV/sidebar.py +++ b/PiTV/sidebar.py @@ -1,5 +1,5 @@ """ -Sidebar, ListTile and WeatherBox template for PiTV, depends on sidebar.glade +Sidebar, ListTile and WeatherBox template for PiTV, depends on sidebar.glade, weather_box.glade, list_tile.glade """ import os @@ -47,7 +47,7 @@ def __init__(self, stack, **kwargs): self.stack = stack self.index = 0 - # TODO:Better solution for this + # TODO:Better solution for this (critical) self.actions = self.get_children()[0].get_children()[ 0].get_children()[0] diff --git a/PiTV/weather.py b/PiTV/weather.py new file mode 100644 index 0000000..ccf411a --- /dev/null +++ b/PiTV/weather.py @@ -0,0 +1,31 @@ +from requests import get +from json import loads + + +class Weather: + def __init__(self, apikey, measure, location): + self.apikey = apikey + self.measure = measure + self.location = location + + self.url = "https://api.openweathermap.org/data/2.5/weather?"\ + "q={}&units={}&appid={}" + self.response = get(self.url.format(location, measure, apikey)).text + + self.parsed = loads(self.response) + + def refresh(self): + self.response = get(self.url.format( + self.location, + self.measure, + self.apikey + )).text + self.parsed = loads(self.response) + + @property + def temperature(self): + return self.parsed["main"]["temp"] + + @property + def icon_code(self): + return self.parsed["weather"][0]["icon"] diff --git a/README.md b/README.md index 62b3d25..43ebdd1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # PiTV New version of SmartTV. Better code, faster, optimized. -__Currently, it doesn't work, I have deleted the dependency, fix is comming soon!__ ## Installation diff --git a/bla.json b/bla.json deleted file mode 100644 index 3904bc4..0000000 --- a/bla.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "status": "ok", - "status_message": "Query was successful", - "data": { - "movie_count": 1, - "limit": 20, - "page_number": 1, - "movies": [ - { - "id": 1632, - "url": "\/movie\/interstellar-2014", - "imdb_code": "tt0816692", - "title": "Interstellar", - "title_english": "Interstellar", - "title_long": "Interstellar (2014)", - "slug": "interstellar-2014", - "year": 2014, - "rating": 8.6, - "runtime": 169, - "genres": [ - "Action", - "Adventure", - "Drama", - "Sci-Fi" - ], - "summary": "Earth's future has been riddled by disasters, famines, and droughts. There is only one way to ensure mankind's survival: Interstellar travel. A newly discovered wormhole in the far reaches of our solar system allows a team of astronauts to go where no man has gone before, a planet that may have the right environment to sustain human life.", - "description_full": "Earth's future has been riddled by disasters, famines, and droughts. There is only one way to ensure mankind's survival: Interstellar travel. A newly discovered wormhole in the far reaches of our solar system allows a team of astronauts to go where no man has gone before, a planet that may have the right environment to sustain human life.", - "synopsis": "Earth's future has been riddled by disasters, famines, and droughts. There is only one way to ensure mankind's survival: Interstellar travel. A newly discovered wormhole in the far reaches of our solar system allows a team of astronauts to go where no man has gone before, a planet that may have the right environment to sustain human life.", - "yt_trailer_code": "2LqzF5WauAw", - "language": "English", - "mpa_rating": "PG-13", - "background_image": "\/assets\/images\/movies\/interstellar_2014\/background.jpg", - "background_image_original": "\/assets\/images\/movies\/interstellar_2014\/background.jpg", - "small_cover_image": "\/assets\/images\/movies\/interstellar_2014\/small-cover.jpg", - "medium_cover_image": "\/assets\/images\/movies\/interstellar_2014\/medium-cover.jpg", - "large_cover_image": "\/assets\/images\/movies\/interstellar_2014\/large-cover.jpg", - "state": "ok", - "torrents": [ - { - "url": "\/torrent\/download\/6E88B3F25BA49D483D740A652BF013C341BC5373", - "hash": "6E88B3F25BA49D483D740A652BF013C341BC5373", - "quality": "720p", - "type": "bluray", - "seeds": 801, - "peers": 142, - "size": "1.02 GB", - "size_bytes": 1095216660, - "date_uploaded": "2015-11-01 00:18:06", - "date_uploaded_unix": 1446333486 - }, - { - "url": "\/torrent\/download\/89599BF4DC369A3A8ECA26411C5CCF922D78B486", - "hash": "89599BF4DC369A3A8ECA26411C5CCF922D78B486", - "quality": "1080p", - "type": "bluray", - "seeds": 1344, - "peers": 237, - "size": "2.26 GB", - "size_bytes": 2426656522, - "date_uploaded": "2015-11-01 00:18:07", - "date_uploaded_unix": 1446333487 - } - ], - "date_uploaded": "2015-11-01 00:18:06", - "date_uploaded_unix": 1446333486 - } - ] - }, - "@meta": { - "server_time": 1585151462, - "server_timezone": "CET", - "api_version": 2, - "execution_time": "0 ms" - } -} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 02e8fa6..71cf202 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ entertainment -screeninfo \ No newline at end of file +screeninfo +easysettings[all] \ No newline at end of file