Merge pull request 'develop' (#12) from develop into master
Reviewed-on: #12
This commit is contained in:
commit
a5f864b802
|
@ -12,7 +12,7 @@ Copy the share/solarfm folder to your user .config/ directory too.
|
||||||
|
|
||||||
<h6>Install Setup</h6>
|
<h6>Install Setup</h6>
|
||||||
```
|
```
|
||||||
sudo apt-get install python3.8 python3-setproctitle python3-gi wget ffmpegthumbnailer steamcmd
|
sudo apt-get install xclip python3.8 python3-setproctitle python3-gi wget ffmpegthumbnailer steamcmd
|
||||||
```
|
```
|
||||||
|
|
||||||
# Known Issues
|
# Known Issues
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
"""
|
||||||
|
Pligin Module
|
||||||
|
"""
|
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
def get_imdb_id(uniqueids):
|
||||||
|
imdb_id = uniqueids.get('imdb')
|
||||||
|
if not imdb_id or not imdb_id.startswith('tt'):
|
||||||
|
return None
|
||||||
|
return imdb_id
|
||||||
|
|
||||||
|
# example format for scraper results
|
||||||
|
_ScraperResults = {
|
||||||
|
'info',
|
||||||
|
'ratings',
|
||||||
|
'uniqueids',
|
||||||
|
'cast',
|
||||||
|
'available_art',
|
||||||
|
'error',
|
||||||
|
'warning' # not handled
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
# coding: utf-8
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020, Team Kodi
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""Functions to interact with various web site APIs."""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
# from pprint import pformat
|
||||||
|
try: #PY2 / PY3
|
||||||
|
from urllib2 import Request, urlopen
|
||||||
|
from urllib2 import URLError
|
||||||
|
from urllib import urlencode
|
||||||
|
except ImportError:
|
||||||
|
from urllib.request import Request, urlopen
|
||||||
|
from urllib.error import URLError
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
try:
|
||||||
|
from typing import Text, Optional, Union, List, Dict, Any # pylint: disable=unused-import
|
||||||
|
InfoType = Dict[Text, Any] # pylint: disable=invalid-name
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
HEADERS = {}
|
||||||
|
|
||||||
|
|
||||||
|
def set_headers(headers):
|
||||||
|
HEADERS.update(headers)
|
||||||
|
|
||||||
|
|
||||||
|
def load_info(url, params=None, default=None, resp_type = 'json'):
|
||||||
|
# type: (Text, Optional[Dict[Text, Union[Text, List[Text]]]]) -> Union[dict, list]
|
||||||
|
"""
|
||||||
|
Load info from external api
|
||||||
|
|
||||||
|
:param url: API endpoint URL
|
||||||
|
:param params: URL query params
|
||||||
|
:default: object to return if there is an error
|
||||||
|
:resp_type: what to return to the calling function
|
||||||
|
:return: API response or default on error
|
||||||
|
"""
|
||||||
|
theerror = ''
|
||||||
|
if params:
|
||||||
|
url = url + '?' + urlencode(params)
|
||||||
|
req = Request(url, headers=HEADERS)
|
||||||
|
try:
|
||||||
|
response = urlopen(req)
|
||||||
|
except URLError as e:
|
||||||
|
if hasattr(e, 'reason'):
|
||||||
|
theerror = {'error': 'failed to reach the remote site\nReason: {}'.format(e.reason)}
|
||||||
|
elif hasattr(e, 'code'):
|
||||||
|
theerror = {'error': 'remote site unable to fulfill the request\nError code: {}'.format(e.code)}
|
||||||
|
if default is not None:
|
||||||
|
return default
|
||||||
|
else:
|
||||||
|
return theerror
|
||||||
|
if resp_type.lower() == 'json':
|
||||||
|
resp = json.loads(response.read().decode('utf-8'))
|
||||||
|
else:
|
||||||
|
resp = response.read().decode('utf-8')
|
||||||
|
return resp
|
|
@ -0,0 +1,87 @@
|
||||||
|
from . import api_utils
|
||||||
|
try:
|
||||||
|
from urllib import quote
|
||||||
|
except ImportError: # py2 / py3
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
API_KEY = '384afe262ee0962545a752ff340e3ce4'
|
||||||
|
API_URL = 'https://webservice.fanart.tv/v3/movies/{}'
|
||||||
|
|
||||||
|
ARTMAP = {
|
||||||
|
'movielogo': 'clearlogo',
|
||||||
|
'hdmovielogo': 'clearlogo',
|
||||||
|
'hdmovieclearart': 'clearart',
|
||||||
|
'movieart': 'clearart',
|
||||||
|
'moviedisc': 'discart',
|
||||||
|
'moviebanner': 'banner',
|
||||||
|
'moviethumb': 'landscape',
|
||||||
|
'moviebackground': 'fanart',
|
||||||
|
'movieposter': 'poster'
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_details(uniqueids, clientkey, language, set_tmdbid):
|
||||||
|
media_id = _get_mediaid(uniqueids)
|
||||||
|
if not media_id:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
movie_data = _get_data(media_id, clientkey)
|
||||||
|
movieset_data = _get_data(set_tmdbid, clientkey)
|
||||||
|
if not movie_data and not movieset_data:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
movie_art = {}
|
||||||
|
movieset_art = {}
|
||||||
|
if movie_data:
|
||||||
|
movie_art = _parse_data(movie_data, language)
|
||||||
|
if movieset_data:
|
||||||
|
movieset_art = _parse_data(movieset_data, language)
|
||||||
|
movieset_art = {'set.' + key: value for key, value in movieset_art.items()}
|
||||||
|
|
||||||
|
available_art = movie_art
|
||||||
|
available_art.update(movieset_art)
|
||||||
|
|
||||||
|
return {'available_art': available_art}
|
||||||
|
|
||||||
|
def _get_mediaid(uniqueids):
|
||||||
|
for source in ('tmdb', 'imdb', 'unknown'):
|
||||||
|
if source in uniqueids:
|
||||||
|
return uniqueids[source]
|
||||||
|
|
||||||
|
def _get_data(media_id, clientkey):
|
||||||
|
headers = {'api-key': API_KEY}
|
||||||
|
if clientkey:
|
||||||
|
headers['client-key'] = clientkey
|
||||||
|
api_utils.set_headers(headers)
|
||||||
|
fanarttv_url = API_URL.format(media_id)
|
||||||
|
return api_utils.load_info(fanarttv_url, default={})
|
||||||
|
|
||||||
|
def _parse_data(data, language):
|
||||||
|
result = {}
|
||||||
|
for arttype, artlist in data.items():
|
||||||
|
if arttype not in ARTMAP:
|
||||||
|
continue
|
||||||
|
for image in artlist:
|
||||||
|
image_lang = _get_imagelanguage(arttype, image)
|
||||||
|
if image_lang and image_lang != language:
|
||||||
|
continue
|
||||||
|
|
||||||
|
generaltype = ARTMAP[arttype]
|
||||||
|
if generaltype == 'poster' and not image_lang:
|
||||||
|
generaltype = 'keyart'
|
||||||
|
if artlist and generaltype not in result:
|
||||||
|
result[generaltype] = []
|
||||||
|
|
||||||
|
url = quote(image['url'], safe="%/:=&?~#+!$,;'@()*[]")
|
||||||
|
resultimage = {'url': url, 'preview': url.replace('.fanart.tv/fanart/', '.fanart.tv/preview/')}
|
||||||
|
result[generaltype].append(resultimage)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _get_imagelanguage(arttype, image):
|
||||||
|
if 'lang' not in image or arttype == 'moviebackground':
|
||||||
|
return None
|
||||||
|
if arttype in ('movielogo', 'hdmovielogo', 'hdmovieclearart', 'movieart', 'moviebanner',
|
||||||
|
'moviethumb', 'moviedisc'):
|
||||||
|
return image['lang'] if image['lang'] not in ('', '00') else 'en'
|
||||||
|
# movieposter may or may not have a title and thus need a language
|
||||||
|
return image['lang'] if image['lang'] not in ('', '00') else None
|
|
@ -0,0 +1,72 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020, Team Kodi
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# IMDb ratings based on code in metadata.themoviedb.org.python by Team Kodi
|
||||||
|
# pylint: disable=missing-docstring
|
||||||
|
|
||||||
|
import re
|
||||||
|
from . import api_utils
|
||||||
|
from . import get_imdb_id
|
||||||
|
|
||||||
|
IMDB_RATINGS_URL = 'https://www.imdb.com/title/{}/'
|
||||||
|
IMDB_RATING_REGEX = re.compile(r'itemprop="ratingValue".*?>.*?([\d.]+).*?<')
|
||||||
|
IMDB_VOTES_REGEX = re.compile(r'itemprop="ratingCount".*?>.*?([\d,]+).*?<')
|
||||||
|
IMDB_TOP250_REGEX = re.compile(r'Top Rated Movies #(\d+)')
|
||||||
|
|
||||||
|
def get_details(uniqueids):
|
||||||
|
imdb_id = get_imdb_id(uniqueids)
|
||||||
|
if not imdb_id:
|
||||||
|
return {}
|
||||||
|
votes, rating, top250 = _get_ratinginfo(imdb_id)
|
||||||
|
return _assemble_imdb_result(votes, rating, top250)
|
||||||
|
|
||||||
|
def _get_ratinginfo(imdb_id):
|
||||||
|
response = api_utils.load_info(IMDB_RATINGS_URL.format(imdb_id), default = '', resp_type='text')
|
||||||
|
return _parse_imdb_result(response)
|
||||||
|
|
||||||
|
def _assemble_imdb_result(votes, rating, top250):
|
||||||
|
result = {}
|
||||||
|
if top250:
|
||||||
|
result['info'] = {'top250': top250}
|
||||||
|
if votes and rating:
|
||||||
|
result['ratings'] = {'imdb': {'votes': votes, 'rating': rating}}
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _parse_imdb_result(input_html):
|
||||||
|
rating = _parse_imdb_rating(input_html)
|
||||||
|
votes = _parse_imdb_votes(input_html)
|
||||||
|
top250 = _parse_imdb_top250(input_html)
|
||||||
|
|
||||||
|
return votes, rating, top250
|
||||||
|
|
||||||
|
def _parse_imdb_rating(input_html):
|
||||||
|
match = re.search(IMDB_RATING_REGEX, input_html)
|
||||||
|
if (match):
|
||||||
|
return float(match.group(1))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _parse_imdb_votes(input_html):
|
||||||
|
match = re.search(IMDB_VOTES_REGEX, input_html)
|
||||||
|
if (match):
|
||||||
|
return int(match.group(1).replace(',', ''))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _parse_imdb_top250(input_html):
|
||||||
|
match = re.search(IMDB_TOP250_REGEX, input_html)
|
||||||
|
if (match):
|
||||||
|
return int(match.group(1))
|
||||||
|
return None
|
|
@ -0,0 +1,249 @@
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from . import tmdbapi
|
||||||
|
|
||||||
|
|
||||||
|
class TMDBMovieScraper(object):
|
||||||
|
def __init__(self, url_settings, language, certification_country):
|
||||||
|
self.url_settings = url_settings
|
||||||
|
self.language = language
|
||||||
|
self.certification_country = certification_country
|
||||||
|
self._urls = None
|
||||||
|
self.tmdbapi = tmdbapi
|
||||||
|
|
||||||
|
@property
|
||||||
|
def urls(self):
|
||||||
|
if not self._urls:
|
||||||
|
self._urls = _load_base_urls(self.url_settings)
|
||||||
|
return self._urls
|
||||||
|
|
||||||
|
def search(self, title, year=None):
|
||||||
|
search_media_id = _parse_media_id(title)
|
||||||
|
if search_media_id:
|
||||||
|
if search_media_id['type'] == 'tmdb':
|
||||||
|
result = _get_movie(search_media_id['id'], self.language, True)
|
||||||
|
result = [result]
|
||||||
|
else:
|
||||||
|
response = tmdbapi.find_movie_by_external_id(search_media_id['id'], language=self.language)
|
||||||
|
theerror = response.get('error')
|
||||||
|
if theerror:
|
||||||
|
return 'error: {}'.format(theerror)
|
||||||
|
result = response.get('movie_results')
|
||||||
|
if 'error' in result:
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
response = tmdbapi.search_movie(query=title, year=year, language=self.language)
|
||||||
|
theerror = response.get('error')
|
||||||
|
if theerror:
|
||||||
|
return 'error: {}'.format(theerror)
|
||||||
|
result = response['results']
|
||||||
|
urls = self.urls
|
||||||
|
|
||||||
|
def is_best(item):
|
||||||
|
return item['title'].lower() == title and (
|
||||||
|
not year or item.get('release_date', '').startswith(year))
|
||||||
|
if result and not is_best(result[0]):
|
||||||
|
best_first = next((item for item in result if is_best(item)), None)
|
||||||
|
if best_first:
|
||||||
|
result = [best_first] + [item for item in result if item is not best_first]
|
||||||
|
|
||||||
|
for item in result:
|
||||||
|
if item.get('poster_path'):
|
||||||
|
item['poster_path'] = urls['preview'] + item['poster_path']
|
||||||
|
if item.get('backdrop_path'):
|
||||||
|
item['backdrop_path'] = urls['preview'] + item['backdrop_path']
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_details(self, uniqueids):
|
||||||
|
media_id = uniqueids.get('tmdb') or uniqueids.get('imdb')
|
||||||
|
details = self._gather_details(media_id)
|
||||||
|
if not details:
|
||||||
|
return None
|
||||||
|
if details.get('error'):
|
||||||
|
return details
|
||||||
|
return self._assemble_details(**details)
|
||||||
|
|
||||||
|
def _gather_details(self, media_id):
|
||||||
|
movie = _get_movie(media_id, self.language)
|
||||||
|
if not movie or movie.get('error'):
|
||||||
|
return movie
|
||||||
|
|
||||||
|
# Don't specify language to get English text for fallback
|
||||||
|
movie_fallback = _get_movie(media_id)
|
||||||
|
|
||||||
|
collection = _get_moviecollection(movie['belongs_to_collection'].get('id'), self.language) if \
|
||||||
|
movie['belongs_to_collection'] else None
|
||||||
|
collection_fallback = _get_moviecollection(movie['belongs_to_collection'].get('id')) if \
|
||||||
|
movie['belongs_to_collection'] else None
|
||||||
|
|
||||||
|
return {'movie': movie, 'movie_fallback': movie_fallback, 'collection': collection,
|
||||||
|
'collection_fallback': collection_fallback}
|
||||||
|
|
||||||
|
def _assemble_details(self, movie, movie_fallback, collection, collection_fallback):
|
||||||
|
info = {
|
||||||
|
'title': movie['title'],
|
||||||
|
'originaltitle': movie['original_title'],
|
||||||
|
'plot': movie.get('overview') or movie_fallback.get('overview'),
|
||||||
|
'tagline': movie.get('tagline') or movie_fallback.get('tagline'),
|
||||||
|
'studio': _get_names(movie['production_companies']),
|
||||||
|
'genre': _get_names(movie['genres']),
|
||||||
|
'country': _get_names(movie['production_countries']),
|
||||||
|
'credits': _get_cast_members(movie['casts'], 'crew', 'Writing', ['Screenplay', 'Writer', 'Author']),
|
||||||
|
'director': _get_cast_members(movie['casts'], 'crew', 'Directing', ['Director']),
|
||||||
|
'premiered': movie['release_date'],
|
||||||
|
'tag': _get_names(movie['keywords']['keywords'])
|
||||||
|
}
|
||||||
|
|
||||||
|
if 'countries' in movie['releases']:
|
||||||
|
certcountry = self.certification_country.upper()
|
||||||
|
for country in movie['releases']['countries']:
|
||||||
|
if country['iso_3166_1'] == certcountry and country['certification']:
|
||||||
|
info['mpaa'] = country['certification']
|
||||||
|
break
|
||||||
|
|
||||||
|
trailer = _parse_trailer(movie.get('trailers', {}), movie_fallback.get('trailers', {}))
|
||||||
|
if trailer:
|
||||||
|
info['trailer'] = trailer
|
||||||
|
if collection:
|
||||||
|
info['set'] = collection.get('name') or collection_fallback.get('name')
|
||||||
|
info['setoverview'] = collection.get('overview') or collection_fallback.get('overview')
|
||||||
|
if movie.get('runtime'):
|
||||||
|
info['duration'] = movie['runtime'] * 60
|
||||||
|
|
||||||
|
ratings = {'themoviedb': {'rating': float(movie['vote_average']), 'votes': int(movie['vote_count'])}}
|
||||||
|
uniqueids = {'tmdb': movie['id'], 'imdb': movie['imdb_id']}
|
||||||
|
cast = [{
|
||||||
|
'name': actor['name'],
|
||||||
|
'role': actor['character'],
|
||||||
|
'thumbnail': self.urls['original'] + actor['profile_path']
|
||||||
|
if actor['profile_path'] else "",
|
||||||
|
'order': actor['order']
|
||||||
|
}
|
||||||
|
for actor in movie['casts'].get('cast', [])
|
||||||
|
]
|
||||||
|
available_art = _parse_artwork(movie, collection, self.urls, self.language)
|
||||||
|
|
||||||
|
_info = {'set_tmdbid': movie['belongs_to_collection'].get('id')
|
||||||
|
if movie['belongs_to_collection'] else None}
|
||||||
|
|
||||||
|
return {'info': info, 'ratings': ratings, 'uniqueids': uniqueids, 'cast': cast,
|
||||||
|
'available_art': available_art, '_info': _info}
|
||||||
|
|
||||||
|
def _parse_media_id(title):
|
||||||
|
if title.startswith('tt') and title[2:].isdigit():
|
||||||
|
return {'type': 'imdb', 'id':title} # IMDB ID works alone because it is clear
|
||||||
|
title = title.lower()
|
||||||
|
if title.startswith('tmdb/') and title[5:].isdigit(): # TMDB ID
|
||||||
|
return {'type': 'tmdb', 'id':title[5:]}
|
||||||
|
elif title.startswith('imdb/tt') and title[7:].isdigit(): # IMDB ID with prefix to match
|
||||||
|
return {'type': 'imdb', 'id':title[5:]}
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_movie(mid, language=None, search=False):
|
||||||
|
details = None if search else \
|
||||||
|
'trailers,images,releases,casts,keywords' if language is not None else \
|
||||||
|
'trailers'
|
||||||
|
response = tmdbapi.get_movie(mid, language=language, append_to_response=details)
|
||||||
|
theerror = response.get('error')
|
||||||
|
if theerror:
|
||||||
|
return 'error: {}'.format(theerror)
|
||||||
|
else:
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _get_moviecollection(collection_id, language=None):
|
||||||
|
if not collection_id:
|
||||||
|
return None
|
||||||
|
details = 'images'
|
||||||
|
response = tmdbapi.get_collection(collection_id, language=language, append_to_response=details)
|
||||||
|
theerror = response.get('error')
|
||||||
|
if theerror:
|
||||||
|
return 'error: {}'.format(theerror)
|
||||||
|
else:
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _parse_artwork(movie, collection, urlbases, language):
|
||||||
|
if language:
|
||||||
|
# Image languages don't have regional variants
|
||||||
|
language = language.split('-')[0]
|
||||||
|
posters = []
|
||||||
|
landscape = []
|
||||||
|
fanart = []
|
||||||
|
if 'images' in movie:
|
||||||
|
posters = _get_images_with_fallback(movie['images']['posters'], urlbases, language)
|
||||||
|
landscape = _get_images(movie['images']['backdrops'], urlbases, language)
|
||||||
|
fanart = _get_images(movie['images']['backdrops'], urlbases, None)
|
||||||
|
|
||||||
|
setposters = []
|
||||||
|
setlandscape = []
|
||||||
|
setfanart = []
|
||||||
|
if collection and 'images' in collection:
|
||||||
|
setposters = _get_images_with_fallback(collection['images']['posters'], urlbases, language)
|
||||||
|
setlandscape = _get_images(collection['images']['backdrops'], urlbases, language)
|
||||||
|
setfanart = _get_images(collection['images']['backdrops'], urlbases, None)
|
||||||
|
|
||||||
|
return {'poster': posters, 'landscape': landscape, 'fanart': fanart,
|
||||||
|
'set.poster': setposters, 'set.landscape': setlandscape, 'set.fanart': setfanart}
|
||||||
|
|
||||||
|
def _get_images_with_fallback(imagelist, urlbases, language, language_fallback='en'):
|
||||||
|
images = _get_images(imagelist, urlbases, language)
|
||||||
|
|
||||||
|
# Add backup images
|
||||||
|
if language != language_fallback:
|
||||||
|
images.extend(_get_images(imagelist, urlbases, language_fallback))
|
||||||
|
|
||||||
|
# Add any images if nothing set so far
|
||||||
|
if not images:
|
||||||
|
images = _get_images(imagelist, urlbases)
|
||||||
|
|
||||||
|
return images
|
||||||
|
|
||||||
|
def _get_images(imagelist, urlbases, language='_any'):
|
||||||
|
result = []
|
||||||
|
for img in imagelist:
|
||||||
|
if language != '_any' and img['iso_639_1'] != language:
|
||||||
|
continue
|
||||||
|
result.append({
|
||||||
|
'url': urlbases['original'] + img['file_path'],
|
||||||
|
'preview': urlbases['preview'] + img['file_path'],
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _get_date_numeric(datetime_):
|
||||||
|
return (datetime_ - datetime(1970, 1, 1)).total_seconds()
|
||||||
|
|
||||||
|
def _load_base_urls(url_settings):
|
||||||
|
urls = {}
|
||||||
|
# urls['original'] = url_settings.getSettingString('originalUrl')
|
||||||
|
# urls['preview'] = url_settings.getSettingString('previewUrl')
|
||||||
|
# last_updated = url_settings.getSettingString('lastUpdated')
|
||||||
|
urls['original'] = ""
|
||||||
|
urls['preview'] = ""
|
||||||
|
last_updated = "0"
|
||||||
|
|
||||||
|
if not urls['original'] or not urls['preview'] or not last_updated or \
|
||||||
|
float(last_updated) < _get_date_numeric(datetime.now() - timedelta(days=30)):
|
||||||
|
conf = tmdbapi.get_configuration()
|
||||||
|
if conf:
|
||||||
|
urls['original'] = conf['images']['secure_base_url'] + 'original'
|
||||||
|
urls['preview'] = conf['images']['secure_base_url'] + 'w780'
|
||||||
|
# url_settings.setSetting('originalUrl', urls['original'])
|
||||||
|
# url_settings.setSetting('previewUrl', urls['preview'])
|
||||||
|
# url_settings.setSetting('lastUpdated', str(_get_date_numeric(datetime.now())))
|
||||||
|
return urls
|
||||||
|
|
||||||
|
def _parse_trailer(trailers, fallback):
|
||||||
|
if trailers.get('youtube'):
|
||||||
|
return 'plugin://plugin.video.youtube/?action=play_video&videoid='+trailers['youtube'][0]['source']
|
||||||
|
if fallback.get('youtube'):
|
||||||
|
return 'plugin://plugin.video.youtube/?action=play_video&videoid='+fallback['youtube'][0]['source']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_names(items):
|
||||||
|
return [item['name'] for item in items] if items else []
|
||||||
|
|
||||||
|
def _get_cast_members(casts, casttype, department, jobs):
|
||||||
|
result = []
|
||||||
|
if casttype in casts:
|
||||||
|
for cast in casts[casttype]:
|
||||||
|
if cast['department'] == department and cast['job'] in jobs and cast['name'] not in result:
|
||||||
|
result.append(cast['name'])
|
||||||
|
return result
|
|
@ -0,0 +1,129 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020, Team Kodi
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
# pylint: disable=missing-docstring
|
||||||
|
|
||||||
|
"""Functions to interact with TMDb API."""
|
||||||
|
|
||||||
|
from . import api_utils
|
||||||
|
try:
|
||||||
|
from typing import Optional, Text, Dict, List, Any # pylint: disable=unused-import
|
||||||
|
InfoType = Dict[Text, Any] # pylint: disable=invalid-name
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
HEADERS = (
|
||||||
|
('User-Agent', 'Kodi Movie scraper by Team Kodi'),
|
||||||
|
('Accept', 'application/json'),
|
||||||
|
)
|
||||||
|
api_utils.set_headers(dict(HEADERS))
|
||||||
|
|
||||||
|
TMDB_PARAMS = {'api_key': 'f090bb54758cabf231fb605d3e3e0468'}
|
||||||
|
BASE_URL = 'https://api.themoviedb.org/3/{}'
|
||||||
|
SEARCH_URL = BASE_URL.format('search/movie')
|
||||||
|
FIND_URL = BASE_URL.format('find/{}')
|
||||||
|
MOVIE_URL = BASE_URL.format('movie/{}')
|
||||||
|
COLLECTION_URL = BASE_URL.format('collection/{}')
|
||||||
|
CONFIG_URL = BASE_URL.format('configuration')
|
||||||
|
|
||||||
|
|
||||||
|
def search_movie(query, year=None, language=None):
|
||||||
|
# type: (Text) -> List[InfoType]
|
||||||
|
"""
|
||||||
|
Search for a movie
|
||||||
|
|
||||||
|
:param title: movie title to search
|
||||||
|
:param year: the year to search (optional)
|
||||||
|
:param language: the language filter for TMDb (optional)
|
||||||
|
:return: a list with found movies
|
||||||
|
"""
|
||||||
|
theurl = SEARCH_URL
|
||||||
|
params = _set_params(None, language)
|
||||||
|
params['query'] = query
|
||||||
|
if year is not None:
|
||||||
|
params['year'] = str(year)
|
||||||
|
return api_utils.load_info(theurl, params=params)
|
||||||
|
|
||||||
|
|
||||||
|
def find_movie_by_external_id(external_id, language=None):
|
||||||
|
# type: (Text) -> List[InfoType]
|
||||||
|
"""
|
||||||
|
Find movie based on external ID
|
||||||
|
|
||||||
|
:param mid: external ID
|
||||||
|
:param language: the language filter for TMDb (optional)
|
||||||
|
:return: the movie or error
|
||||||
|
"""
|
||||||
|
theurl = FIND_URL.format(external_id)
|
||||||
|
params = _set_params(None, language)
|
||||||
|
params['external_source'] = 'imdb_id'
|
||||||
|
return api_utils.load_info(theurl, params=params)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_movie(mid, language=None, append_to_response=None):
|
||||||
|
# type: (Text) -> List[InfoType]
|
||||||
|
"""
|
||||||
|
Get movie details
|
||||||
|
|
||||||
|
:param mid: TMDb movie ID
|
||||||
|
:param language: the language filter for TMDb (optional)
|
||||||
|
:append_to_response: the additional data to get from TMDb (optional)
|
||||||
|
:return: the movie or error
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
theurl = MOVIE_URL.format(mid)
|
||||||
|
return api_utils.load_info(theurl, params=_set_params(append_to_response, language))
|
||||||
|
except Exception as e:
|
||||||
|
print(repr(e))
|
||||||
|
|
||||||
|
|
||||||
|
def get_collection(collection_id, language=None, append_to_response=None):
|
||||||
|
# type: (Text) -> List[InfoType]
|
||||||
|
"""
|
||||||
|
Get movie collection information
|
||||||
|
|
||||||
|
:param collection_id: TMDb collection ID
|
||||||
|
:param language: the language filter for TMDb (optional)
|
||||||
|
:append_to_response: the additional data to get from TMDb (optional)
|
||||||
|
:return: the movie or error
|
||||||
|
"""
|
||||||
|
theurl = COLLECTION_URL.format(collection_id)
|
||||||
|
return api_utils.load_info(theurl, params=_set_params(append_to_response, language))
|
||||||
|
|
||||||
|
|
||||||
|
def get_configuration():
|
||||||
|
# type: (Text) -> List[InfoType]
|
||||||
|
"""
|
||||||
|
Get configuration information
|
||||||
|
|
||||||
|
:return: configuration details or error
|
||||||
|
"""
|
||||||
|
return api_utils.load_info(CONFIG_URL, params=TMDB_PARAMS.copy())
|
||||||
|
|
||||||
|
|
||||||
|
def _set_params(append_to_response, language):
|
||||||
|
params = TMDB_PARAMS.copy()
|
||||||
|
img_lang = 'en,null'
|
||||||
|
if language is not None:
|
||||||
|
params['language'] = language
|
||||||
|
img_lang = '%s,en,null' % language[0:2]
|
||||||
|
if append_to_response is not None:
|
||||||
|
params['append_to_response'] = append_to_response
|
||||||
|
if 'images' in append_to_response:
|
||||||
|
params['include_image_language'] = img_lang
|
||||||
|
return params
|
|
@ -0,0 +1,55 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020, Team Kodi
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
# pylint: disable=missing-docstring
|
||||||
|
|
||||||
|
"""Functions to interact with Trakt API."""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
from . import api_utils
|
||||||
|
from . import get_imdb_id
|
||||||
|
try:
|
||||||
|
from typing import Optional, Text, Dict, List, Any # pylint: disable=unused-import
|
||||||
|
InfoType = Dict[Text, Any] # pylint: disable=invalid-name
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
HEADERS = (
|
||||||
|
('User-Agent', 'Kodi Movie scraper by Team Kodi'),
|
||||||
|
('Accept', 'application/json'),
|
||||||
|
('trakt-api-key', '5f2dc73b6b11c2ac212f5d8b4ec8f3dc4b727bb3f026cd254d89eda997fe64ae'),
|
||||||
|
('trakt-api-version', '2'),
|
||||||
|
('Content-Type', 'application/json'),
|
||||||
|
)
|
||||||
|
api_utils.set_headers(dict(HEADERS))
|
||||||
|
|
||||||
|
MOVIE_URL = 'https://api.trakt.tv/movies/{}'
|
||||||
|
|
||||||
|
|
||||||
|
def get_trakt_ratinginfo(uniqueids):
|
||||||
|
imdb_id = get_imdb_id(uniqueids)
|
||||||
|
result = {}
|
||||||
|
url = MOVIE_URL.format(imdb_id)
|
||||||
|
params = {'extended': 'full'}
|
||||||
|
movie_info = api_utils.load_info(url, params=params, default={})
|
||||||
|
if(movie_info):
|
||||||
|
if 'votes' in movie_info and 'rating' in movie_info:
|
||||||
|
result['ratings'] = {'trakt': {'votes': int(movie_info['votes']), 'rating': float(movie_info['rating'])}}
|
||||||
|
elif 'rating' in movie_info:
|
||||||
|
result['ratings'] = {'trakt': {'rating': float(movie_info['rating'])}}
|
||||||
|
return result
|
Binary file not shown.
Binary file not shown.
|
@ -77,8 +77,7 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
|
||||||
event_system.subscribe("set_clipboard_data", self.set_clipboard_data)
|
event_system.subscribe("set_clipboard_data", self.set_clipboard_data)
|
||||||
|
|
||||||
def _load_glade_file(self):
|
def _load_glade_file(self):
|
||||||
self.builder = Gtk.Builder()
|
self.builder.add_from_file( settings_manager.get_glade_file() )
|
||||||
self.builder.add_from_file(settings_manager.get_glade_file())
|
|
||||||
self.builder.expose_object("main_window", self.window)
|
self.builder.expose_object("main_window", self.window)
|
||||||
|
|
||||||
self.core_widget = self.builder.get_object("core_widget")
|
self.core_widget = self.builder.get_object("core_widget")
|
||||||
|
|
|
@ -35,13 +35,39 @@ class State:
|
||||||
user_pass_dialog: type = None
|
user_pass_dialog: type = None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SFMBuilder(Gtk.Builder):
|
||||||
|
"""docstring for SFMBuilder."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(SFMBuilder, self).__init__()
|
||||||
|
|
||||||
|
self.objects = {}
|
||||||
|
|
||||||
|
def get_object(self, id: str, use_gtk: bool = True) -> any:
|
||||||
|
if not use_gtk:
|
||||||
|
return self.objects[id]
|
||||||
|
|
||||||
|
return super(SFMBuilder, self).get_object(id)
|
||||||
|
|
||||||
|
def expose_object(self, id: str, object: any, use_gtk: bool = True) -> None:
|
||||||
|
if not use_gtk:
|
||||||
|
self.objects[id] = object
|
||||||
|
else:
|
||||||
|
super(SFMBuilder, self).expose_object(id, object)
|
||||||
|
|
||||||
|
def dereference_object(self, id: str) -> None:
|
||||||
|
del self.objects[id]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Controller_Data:
|
class Controller_Data:
|
||||||
""" Controller_Data contains most of the state of the app at ay given time. It also has some support methods. """
|
""" Controller_Data contains most of the state of the app at ay given time. It also has some support methods. """
|
||||||
__slots__ = "settings", "builder", "logger", "keybindings", "trashman", "fm_controller", "window", "window1", "window2", "window3", "window4"
|
__slots__ = "settings", "builder", "logger", "keybindings", "trashman", "fm_controller", "window", "window1", "window2", "window3", "window4"
|
||||||
|
|
||||||
def _setup_controller_data(self) -> None:
|
def _setup_controller_data(self) -> None:
|
||||||
self.window = settings_manager.get_main_window()
|
self.window = settings_manager.get_main_window()
|
||||||
self.builder = None
|
self.builder = SFMBuilder()
|
||||||
self.core_widget = None
|
self.core_widget = None
|
||||||
|
|
||||||
self._load_glade_file()
|
self._load_glade_file()
|
||||||
|
@ -88,7 +114,7 @@ class Controller_Data:
|
||||||
state.notebooks = self.notebooks
|
state.notebooks = self.notebooks
|
||||||
state.wid, state.tid = self.fm_controller.get_active_wid_and_tid()
|
state.wid, state.tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
state.tab = self.get_fm_window(state.wid).get_tab_by_id(state.tid)
|
state.tab = self.get_fm_window(state.wid).get_tab_by_id(state.tid)
|
||||||
state.icon_grid = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid")
|
state.icon_grid = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid", use_gtk = False)
|
||||||
# state.icon_grid = event_system.emit_and_await("get_files_view_icon_grid", (state.wid, state.tid))
|
# state.icon_grid = event_system.emit_and_await("get_files_view_icon_grid", (state.wid, state.tid))
|
||||||
state.store = state.icon_grid.get_model()
|
state.store = state.icon_grid.get_model()
|
||||||
state.message_dialog = MessageWidget()
|
state.message_dialog = MessageWidget()
|
||||||
|
@ -110,7 +136,7 @@ class Controller_Data:
|
||||||
event_system.emit("update_state_info_plugins", state) # NOTE: Need to remove after we convert plugins to use emit_and_await
|
event_system.emit("update_state_info_plugins", state) # NOTE: Need to remove after we convert plugins to use emit_and_await
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def format_to_uris(self, store, wid, tid, treePaths, use_just_path=False):
|
def format_to_uris(self, store, wid, tid, treePaths, use_just_path = False):
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
dir = tab.get_current_directory()
|
dir = tab.get_current_directory()
|
||||||
uris = []
|
uris = []
|
||||||
|
|
|
@ -35,7 +35,7 @@ class FileActionSignalsMixin:
|
||||||
|
|
||||||
# NOTE: Too lazy to impliment a proper update handler and so just regen store and update tab.
|
# NOTE: Too lazy to impliment a proper update handler and so just regen store and update tab.
|
||||||
# Use a lock system to prevent too many update calls for certain instances but user can manually refresh if they have urgency
|
# Use a lock system to prevent too many update calls for certain instances but user can manually refresh if they have urgency
|
||||||
def dir_watch_updates(self, file_monitor, file, other_file=None, eve_type=None, data=None):
|
def dir_watch_updates(self, file_monitor, file, other_file = None, eve_type = None, data = None):
|
||||||
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
||||||
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
|
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
|
||||||
Gio.FileMonitorEvent.MOVED_OUT]:
|
Gio.FileMonitorEvent.MOVED_OUT]:
|
||||||
|
@ -68,7 +68,7 @@ class FileActionSignalsMixin:
|
||||||
wid, tid = tab_widget.split("|")
|
wid, tid = tab_widget.split("|")
|
||||||
notebook = self.builder.get_object(f"window_{wid}")
|
notebook = self.builder.get_object(f"window_{wid}")
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
icon_grid = self.builder.get_object(f"{wid}|{tid}|icon_grid")
|
icon_grid = self.builder.get_object(f"{wid}|{tid}|icon_grid", use_gtk = False)
|
||||||
store = icon_grid.get_model()
|
store = icon_grid.get_model()
|
||||||
_store, tab_widget_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
|
_store, tab_widget_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ class FileActionSignalsMixin:
|
||||||
self.set_bottom_labels(tab)
|
self.set_bottom_labels(tab)
|
||||||
|
|
||||||
|
|
||||||
def do_file_search(self, widget, eve=None):
|
def do_file_search(self, widget, eve = None):
|
||||||
if not self.ctrl_down and not self.shift_down and not self.alt_down:
|
if not self.ctrl_down and not self.shift_down and not self.alt_down:
|
||||||
target = widget.get_name()
|
target = widget.get_name()
|
||||||
notebook = self.builder.get_object(target)
|
notebook = self.builder.get_object(target)
|
||||||
|
|
|
@ -74,7 +74,7 @@ class KeyboardSignalsMixin:
|
||||||
def keyboard_close_tab(self):
|
def keyboard_close_tab(self):
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
notebook = self.builder.get_object(f"window_{wid}")
|
notebook = self.builder.get_object(f"window_{wid}")
|
||||||
scroll = self.builder.get_object(f"{wid}|{tid}")
|
scroll = self.builder.get_object(f"{wid}|{tid}", use_gtk = False)
|
||||||
page = notebook.page_num(scroll)
|
page = notebook.page_num(scroll)
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
watcher = tab.get_dir_watcher()
|
watcher = tab.get_dir_watcher()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
|
import asyncio
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
|
@ -31,35 +32,56 @@ class GridMixin:
|
||||||
for i, icon in enumerate( self.create_icons_generator(tab, dir, files) ):
|
for i, icon in enumerate( self.create_icons_generator(tab, dir, files) ):
|
||||||
self.load_icon(i, store, icon)
|
self.load_icon(i, store, icon)
|
||||||
else:
|
else:
|
||||||
for i, file in enumerate(files):
|
# for i, file in enumerate(files):
|
||||||
self.create_icon(i, tab, store, dir, file[0])
|
# self.create_icon(i, tab, store, dir, file[0])
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
except RuntimeError:
|
||||||
|
loop = None
|
||||||
|
|
||||||
|
if loop and loop.is_running():
|
||||||
|
loop.create_task( self.create_icons(tab, store, dir, files) )
|
||||||
|
else:
|
||||||
|
asyncio.run( self.create_icons(tab, store, dir, files) )
|
||||||
|
|
||||||
# NOTE: Not likely called often from here but it could be useful
|
# NOTE: Not likely called often from here but it could be useful
|
||||||
if save_state and not trace_debug:
|
if save_state and not trace_debug:
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
|
async def create_icons(self, tab, store, dir, files):
|
||||||
|
tasks = [self.update_store(i, store, dir, tab, file[0]) for i, file in enumerate(files)]
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
async def load_icon(self, i, store, icon):
|
||||||
|
self.update_store(i, store, icon)
|
||||||
|
|
||||||
|
async def update_store(self, i, store, dir, tab, file):
|
||||||
|
icon = tab.create_icon(dir, file)
|
||||||
|
itr = store.get_iter(i)
|
||||||
|
store.set_value(itr, 0, icon)
|
||||||
|
|
||||||
def create_icons_generator(self, tab, dir, files):
|
def create_icons_generator(self, tab, dir, files):
|
||||||
for file in files:
|
for file in files:
|
||||||
icon = tab.create_icon(dir, file[0])
|
icon = tab.create_icon(dir, file[0])
|
||||||
yield icon
|
yield icon
|
||||||
|
|
||||||
@daemon_threaded
|
# @daemon_threaded
|
||||||
def create_icon(self, i, tab, store, dir, file):
|
# def create_icon(self, i, tab, store, dir, file):
|
||||||
icon = tab.create_icon(dir, file)
|
# icon = tab.create_icon(dir, file)
|
||||||
GLib.idle_add(self.update_store, *(i, store, icon,))
|
# GLib.idle_add(self.update_store, *(i, store, icon,))
|
||||||
|
#
|
||||||
|
# @daemon_threaded
|
||||||
|
# def load_icon(self, i, store, icon):
|
||||||
|
# GLib.idle_add(self.update_store, *(i, store, icon,))
|
||||||
|
|
||||||
@daemon_threaded
|
# def update_store(self, i, store, icon):
|
||||||
def load_icon(self, i, store, icon):
|
# itr = store.get_iter(i)
|
||||||
GLib.idle_add(self.update_store, *(i, store, icon,))
|
# store.set_value(itr, 0, icon)
|
||||||
|
|
||||||
def update_store(self, i, store, icon):
|
|
||||||
itr = store.get_iter(i)
|
|
||||||
store.set_value(itr, 0, icon)
|
|
||||||
|
|
||||||
def create_tab_widget(self, tab):
|
def create_tab_widget(self, tab):
|
||||||
return TabHeaderWidget(tab, self.close_tab)
|
return TabHeaderWidget(tab, self.close_tab)
|
||||||
|
|
||||||
def create_scroll_and_store(self, tab, wid, use_tree_view=False):
|
def create_scroll_and_store(self, tab, wid, use_tree_view = False):
|
||||||
scroll = Gtk.ScrolledWindow()
|
scroll = Gtk.ScrolledWindow()
|
||||||
|
|
||||||
if not use_tree_view:
|
if not use_tree_view:
|
||||||
|
@ -71,8 +93,8 @@ class GridMixin:
|
||||||
scroll.add(grid)
|
scroll.add(grid)
|
||||||
scroll.set_name(f"{wid}|{tab.get_id()}")
|
scroll.set_name(f"{wid}|{tab.get_id()}")
|
||||||
grid.set_name(f"{wid}|{tab.get_id()}")
|
grid.set_name(f"{wid}|{tab.get_id()}")
|
||||||
self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid)
|
self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid, use_gtk = False)
|
||||||
self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll)
|
self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll, use_gtk = False)
|
||||||
|
|
||||||
return scroll, grid.get_store()
|
return scroll, grid.get_store()
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from .grid_mixin import GridMixin
|
||||||
class TabMixin(GridMixin):
|
class TabMixin(GridMixin):
|
||||||
"""docstring for TabMixin"""
|
"""docstring for TabMixin"""
|
||||||
|
|
||||||
def create_tab(self, wid=None, tid=None, path=None):
|
def create_tab(self, wid: int = None, tid: int = None, path: str = None):
|
||||||
if not wid:
|
if not wid:
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ class TabMixin(GridMixin):
|
||||||
tab_box = button.get_parent()
|
tab_box = button.get_parent()
|
||||||
wid = int(notebook.get_name()[-1])
|
wid = int(notebook.get_name()[-1])
|
||||||
tid = self.get_id_from_tab_box(tab_box)
|
tid = self.get_id_from_tab_box(tab_box)
|
||||||
scroll = self.builder.get_object(f"{wid}|{tid}")
|
scroll = self.builder.get_object(f"{wid}|{tid}", use_gtk = False)
|
||||||
icon_grid = scroll.get_children()[0]
|
icon_grid = scroll.get_children()[0]
|
||||||
store = icon_grid.get_store()
|
store = icon_grid.get_store()
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
|
@ -69,6 +69,9 @@ class TabMixin(GridMixin):
|
||||||
watcher.cancel()
|
watcher.cancel()
|
||||||
self.get_fm_window(wid).delete_tab_by_id(tid)
|
self.get_fm_window(wid).delete_tab_by_id(tid)
|
||||||
|
|
||||||
|
self.builder.dereference_object(f"{wid}|{tid}|icon_grid")
|
||||||
|
self.builder.dereference_object(f"{wid}|{tid}")
|
||||||
|
|
||||||
store.clear()
|
store.clear()
|
||||||
# store.run_dispose()
|
# store.run_dispose()
|
||||||
icon_grid.destroy()
|
icon_grid.destroy()
|
||||||
|
@ -112,7 +115,7 @@ class TabMixin(GridMixin):
|
||||||
if not settings_manager.is_trace_debug():
|
if not settings_manager.is_trace_debug():
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
def on_tab_switch_update(self, notebook, content=None, index=None):
|
def on_tab_switch_update(self, notebook, content = None, index = None):
|
||||||
self.selected_files.clear()
|
self.selected_files.clear()
|
||||||
wid, tid = content.get_children()[0].get_name().split("|")
|
wid, tid = content.get_children()[0].get_name().split("|")
|
||||||
self.fm_controller.set_wid_and_tid(wid, tid)
|
self.fm_controller.set_wid_and_tid(wid, tid)
|
||||||
|
@ -131,7 +134,7 @@ class TabMixin(GridMixin):
|
||||||
def get_tab_icon_grid_from_notebook(self, notebook):
|
def get_tab_icon_grid_from_notebook(self, notebook):
|
||||||
return notebook.get_children()[1].get_children()[0]
|
return notebook.get_children()[1].get_children()[0]
|
||||||
|
|
||||||
def refresh_tab(data=None):
|
def refresh_tab(data = None):
|
||||||
state = self.get_current_state()
|
state = self.get_current_state()
|
||||||
state.tab.load_directory()
|
state.tab.load_directory()
|
||||||
self.load_store(state.tab, state.store)
|
self.load_store(state.tab, state.store)
|
||||||
|
@ -148,7 +151,7 @@ class TabMixin(GridMixin):
|
||||||
if not settings_manager.is_trace_debug():
|
if not settings_manager.is_trace_debug():
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
def do_action_from_bar_controls(self, widget, eve=None):
|
def do_action_from_bar_controls(self, widget, eve = None):
|
||||||
action = widget.get_name()
|
action = widget.get_name()
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
notebook = self.builder.get_object(f"window_{wid}")
|
notebook = self.builder.get_object(f"window_{wid}")
|
||||||
|
|
|
@ -36,6 +36,15 @@ class UIMixin(PaneMixin, WindowMixin):
|
||||||
isHidden = True if session["window"]["isHidden"] == "True" else False
|
isHidden = True if session["window"]["isHidden"] == "True" else False
|
||||||
event_system.emit("load_files_view_state", (nickname, tabs))
|
event_system.emit("load_files_view_state", (nickname, tabs))
|
||||||
|
|
||||||
|
@daemon_threaded
|
||||||
|
def _focus_last_visible_notebook(self, icon_grid):
|
||||||
|
import time
|
||||||
|
|
||||||
|
window = settings_manager.get_main_window()
|
||||||
|
while not window.is_visible() and not window.get_realized():
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
icon_grid.event(Gdk.Event().new(type = Gdk.EventType.BUTTON_RELEASE))
|
||||||
|
|
||||||
def _current_loading_process(self, session_json = None):
|
def _current_loading_process(self, session_json = None):
|
||||||
if session_json:
|
if session_json:
|
||||||
|
@ -58,16 +67,17 @@ class UIMixin(PaneMixin, WindowMixin):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self.is_pane4_hidden:
|
if not self.is_pane4_hidden:
|
||||||
icon_grid = self.window4.get_children()[-1].get_children()[0]
|
notebook = self.window4
|
||||||
elif not self.is_pane3_hidden:
|
elif not self.is_pane3_hidden:
|
||||||
icon_grid = self.window3.get_children()[-1].get_children()[0]
|
notebook = self.window3
|
||||||
elif not self.is_pane2_hidden:
|
elif not self.is_pane2_hidden:
|
||||||
icon_grid = self.window2.get_children()[-1].get_children()[0]
|
notebook = self.window2
|
||||||
elif not self.is_pane1_hidden:
|
elif not self.is_pane1_hidden:
|
||||||
icon_grid = self.window1.get_children()[-1].get_children()[0]
|
notebook = self.window1
|
||||||
|
|
||||||
icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE))
|
scroll_win = notebook.get_children()[-1]
|
||||||
icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE))
|
icon_grid = scroll_win.get_children()[0]
|
||||||
|
self._focus_last_visible_notebook(icon_grid)
|
||||||
except UIMixinException as e:
|
except UIMixinException as e:
|
||||||
logger.info("\n: The saved session might be missing window data! :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n")
|
logger.info("\n: The saved session might be missing window data! :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n")
|
||||||
logger.debug(repr(e))
|
logger.debug(repr(e))
|
||||||
|
|
|
@ -54,7 +54,7 @@ class FilesWidget(FileActionSignalsMixin, WindowMixin):
|
||||||
self.files_view.set_group_name("files_widget")
|
self.files_view.set_group_name("files_widget")
|
||||||
self.builder.expose_object(f"{self.NAME}", self.files_view)
|
self.builder.expose_object(f"{self.NAME}", self.files_view)
|
||||||
|
|
||||||
def _load_files_view_state(self, win_name=None, tabs=None):
|
def _load_files_view_state(self, win_name = None, tabs = None):
|
||||||
if win_name == self.NAME:
|
if win_name == self.NAME:
|
||||||
if tabs:
|
if tabs:
|
||||||
for tab in tabs:
|
for tab in tabs:
|
||||||
|
@ -62,9 +62,9 @@ class FilesWidget(FileActionSignalsMixin, WindowMixin):
|
||||||
else:
|
else:
|
||||||
self.create_new_tab_notebook(None, self.INDEX, None)
|
self.create_new_tab_notebook(None, self.INDEX, None)
|
||||||
|
|
||||||
def _get_files_view_icon_grid(self, win_index=None, tid=None):
|
def _get_files_view_icon_grid(self, win_index = None, tid = None):
|
||||||
if win_index == str(self.INDEX):
|
if win_index == str(self.INDEX):
|
||||||
return self.builder.get_object(f"{self.INDEX}|{tid}|icon_grid")
|
return self.builder.get_object(f"{self.INDEX}|{tid}|icon_grid", use_gtk = False)
|
||||||
|
|
||||||
|
|
||||||
def set_fm_controller(self, _fm_controller):
|
def set_fm_controller(self, _fm_controller):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
|
import asyncio
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
|
@ -18,7 +19,6 @@ class GridMixin:
|
||||||
"""docstring for GridMixin"""
|
"""docstring for GridMixin"""
|
||||||
|
|
||||||
def load_store(self, tab, store, save_state = False, use_generator = False):
|
def load_store(self, tab, store, save_state = False, use_generator = False):
|
||||||
store.clear()
|
|
||||||
dir = tab.get_current_directory()
|
dir = tab.get_current_directory()
|
||||||
files = tab.get_files()
|
files = tab.get_files()
|
||||||
|
|
||||||
|
@ -32,35 +32,56 @@ class GridMixin:
|
||||||
for i, icon in enumerate( self.create_icons_generator(tab, dir, files) ):
|
for i, icon in enumerate( self.create_icons_generator(tab, dir, files) ):
|
||||||
self.load_icon(i, store, icon)
|
self.load_icon(i, store, icon)
|
||||||
else:
|
else:
|
||||||
for i, file in enumerate(files):
|
# for i, file in enumerate(files):
|
||||||
self.create_icon(i, tab, store, dir, file[0])
|
# self.create_icon(i, tab, store, dir, file[0])
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
except RuntimeError:
|
||||||
|
loop = None
|
||||||
|
|
||||||
|
if loop and loop.is_running():
|
||||||
|
loop.create_task( self.create_icons(tab, store, dir, files) )
|
||||||
|
else:
|
||||||
|
asyncio.run( self.create_icons(tab, store, dir, files) )
|
||||||
|
|
||||||
# NOTE: Not likely called often from here but it could be useful
|
# NOTE: Not likely called often from here but it could be useful
|
||||||
if save_state and not trace_debug:
|
if save_state and not trace_debug:
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
|
async def create_icons(self, tab, store, dir, files):
|
||||||
|
tasks = [self.update_store(i, store, dir, tab, file[0]) for i, file in enumerate(files)]
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
async def load_icon(self, i, store, icon):
|
||||||
|
self.update_store(i, store, icon)
|
||||||
|
|
||||||
|
async def update_store(self, i, store, dir, tab, file):
|
||||||
|
icon = tab.create_icon(dir, file)
|
||||||
|
itr = store.get_iter(i)
|
||||||
|
store.set_value(itr, 0, icon)
|
||||||
|
|
||||||
def create_icons_generator(self, tab, dir, files):
|
def create_icons_generator(self, tab, dir, files):
|
||||||
for file in files:
|
for file in files:
|
||||||
icon = tab.create_icon(dir, file[0])
|
icon = tab.create_icon(dir, file[0])
|
||||||
yield icon
|
yield icon
|
||||||
|
|
||||||
@daemon_threaded
|
# @daemon_threaded
|
||||||
def create_icon(self, i, tab, store, dir, file):
|
# def create_icon(self, i, tab, store, dir, file):
|
||||||
icon = tab.create_icon(dir, file)
|
# icon = tab.create_icon(dir, file)
|
||||||
GLib.idle_add(self.update_store, *(i, store, icon,))
|
# GLib.idle_add(self.update_store, *(i, store, icon,))
|
||||||
|
#
|
||||||
@daemon_threaded
|
# @daemon_threaded
|
||||||
def load_icon(self, i, store, icon):
|
# def load_icon(self, i, store, icon):
|
||||||
GLib.idle_add(self.update_store, *(i, store, icon,))
|
# GLib.idle_add(self.update_store, *(i, store, icon,))
|
||||||
|
#
|
||||||
def update_store(self, i, store, icon):
|
# def update_store(self, i, store, icon):
|
||||||
itr = store.get_iter(i)
|
# itr = store.get_iter(i)
|
||||||
store.set_value(itr, 0, icon)
|
# store.set_value(itr, 0, icon)
|
||||||
|
|
||||||
def create_tab_widget(self, tab):
|
def create_tab_widget(self, tab):
|
||||||
return TabHeaderWidget(tab, self.close_tab)
|
return TabHeaderWidget(tab, self.close_tab)
|
||||||
|
|
||||||
def create_scroll_and_store(self, tab, wid, use_tree_view=False):
|
def create_scroll_and_store(self, tab, wid, use_tree_view = False):
|
||||||
scroll = Gtk.ScrolledWindow()
|
scroll = Gtk.ScrolledWindow()
|
||||||
|
|
||||||
if not use_tree_view:
|
if not use_tree_view:
|
||||||
|
@ -72,8 +93,8 @@ class GridMixin:
|
||||||
scroll.add(grid)
|
scroll.add(grid)
|
||||||
scroll.set_name(f"{wid}|{tab.get_id()}")
|
scroll.set_name(f"{wid}|{tab.get_id()}")
|
||||||
grid.set_name(f"{wid}|{tab.get_id()}")
|
grid.set_name(f"{wid}|{tab.get_id()}")
|
||||||
self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid)
|
self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid, use_gtk = False)
|
||||||
self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll)
|
self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll, use_gtk = False)
|
||||||
|
|
||||||
return scroll, grid.get_store()
|
return scroll, grid.get_store()
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from .grid_mixin import GridMixin
|
||||||
class TabMixin(GridMixin):
|
class TabMixin(GridMixin):
|
||||||
"""docstring for TabMixin"""
|
"""docstring for TabMixin"""
|
||||||
|
|
||||||
def create_tab(self, wid=None, tid=None, path=None):
|
def create_tab(self, wid: int = None, tid: int = None, path: str = None):
|
||||||
if not wid:
|
if not wid:
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ class TabMixin(GridMixin):
|
||||||
tab_box = button.get_parent()
|
tab_box = button.get_parent()
|
||||||
wid = int(notebook.get_name()[-1])
|
wid = int(notebook.get_name()[-1])
|
||||||
tid = self.get_id_from_tab_box(tab_box)
|
tid = self.get_id_from_tab_box(tab_box)
|
||||||
scroll = self.builder.get_object(f"{wid}|{tid}")
|
scroll = self.builder.get_object(f"{wid}|{tid}", use_gtk = False)
|
||||||
icon_grid = scroll.get_children()[0]
|
icon_grid = scroll.get_children()[0]
|
||||||
store = icon_grid.get_model()
|
store = icon_grid.get_model()
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
|
@ -71,6 +71,9 @@ class TabMixin(GridMixin):
|
||||||
watcher.cancel()
|
watcher.cancel()
|
||||||
self.get_fm_window(wid).delete_tab_by_id(tid)
|
self.get_fm_window(wid).delete_tab_by_id(tid)
|
||||||
|
|
||||||
|
self.builder.dereference_object(f"{wid}|{tid}|icon_grid")
|
||||||
|
self.builder.dereference_object(f"{wid}|{tid}")
|
||||||
|
|
||||||
store.clear()
|
store.clear()
|
||||||
# store.run_dispose()
|
# store.run_dispose()
|
||||||
icon_grid.destroy()
|
icon_grid.destroy()
|
||||||
|
@ -114,7 +117,7 @@ class TabMixin(GridMixin):
|
||||||
if not settings_manager.is_trace_debug():
|
if not settings_manager.is_trace_debug():
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
def on_tab_switch_update(self, notebook, content=None, index=None):
|
def on_tab_switch_update(self, notebook, content = None, index = None):
|
||||||
self.selected_files.clear()
|
self.selected_files.clear()
|
||||||
wid, tid = content.get_children()[0].get_name().split("|")
|
wid, tid = content.get_children()[0].get_name().split("|")
|
||||||
self.fm_controller.set_wid_and_tid(wid, tid)
|
self.fm_controller.set_wid_and_tid(wid, tid)
|
||||||
|
@ -133,7 +136,7 @@ class TabMixin(GridMixin):
|
||||||
def get_tab_icon_grid_from_notebook(self, notebook):
|
def get_tab_icon_grid_from_notebook(self, notebook):
|
||||||
return notebook.get_children()[1].get_children()[0]
|
return notebook.get_children()[1].get_children()[0]
|
||||||
|
|
||||||
def refresh_tab(data=None):
|
def refresh_tab(data = None):
|
||||||
state = self.get_current_state()
|
state = self.get_current_state()
|
||||||
state.tab.load_directory()
|
state.tab.load_directory()
|
||||||
self.load_store(state.tab, state.store)
|
self.load_store(state.tab, state.store)
|
||||||
|
@ -150,7 +153,7 @@ class TabMixin(GridMixin):
|
||||||
if not settings_manager.is_trace_debug():
|
if not settings_manager.is_trace_debug():
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
def do_action_from_bar_controls(self, widget, eve=None):
|
def do_action_from_bar_controls(self, widget, eve = None):
|
||||||
action = widget.get_name()
|
action = widget.get_name()
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
notebook = self.builder.get_object(f"window_{wid}")
|
notebook = self.builder.get_object(f"window_{wid}")
|
||||||
|
|
|
@ -82,7 +82,7 @@ class Window(Gtk.ApplicationWindow):
|
||||||
styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||||
|
|
||||||
def _area_draw(self, widget: Gtk.ApplicationWindow, cr: cairo.Context) -> None:
|
def _area_draw(self, widget: Gtk.ApplicationWindow, cr: cairo.Context) -> None:
|
||||||
cr.set_source_rgba(0, 0, 0, 0.54)
|
cr.set_source_rgba( *settings_manager.get_paint_bg_color() )
|
||||||
cr.set_operator(cairo.OPERATOR_SOURCE)
|
cr.set_operator(cairo.OPERATOR_SOURCE)
|
||||||
cr.paint()
|
cr.paint()
|
||||||
cr.set_operator(cairo.OPERATOR_OVER)
|
cr.set_operator(cairo.OPERATOR_OVER)
|
||||||
|
|
|
@ -50,8 +50,8 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
||||||
|
|
||||||
if not thumbnl:
|
if not thumbnl:
|
||||||
# TODO: Detect if not in a thread and use directly for speed get_system_thumbnail
|
# TODO: Detect if not in a thread and use directly for speed get_system_thumbnail
|
||||||
# thumbnl = self.get_system_thumbnail(full_path, self.sys_icon_wh[0])
|
thumbnl = self.get_system_thumbnail(full_path, self.sys_icon_wh[0])
|
||||||
thumbnl = self._get_system_thumbnail_gtk_thread(full_path, self.sys_icon_wh[0])
|
# thumbnl = self._get_system_thumbnail_gtk_thread(full_path, self.sys_icon_wh[0])
|
||||||
if not thumbnl:
|
if not thumbnl:
|
||||||
raise IconException("No known icons found.")
|
raise IconException("No known icons found.")
|
||||||
|
|
||||||
|
@ -174,14 +174,15 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
||||||
return path_exists, img_hash, hash_img_path
|
return path_exists, img_hash, hash_img_path
|
||||||
|
|
||||||
|
|
||||||
def fast_hash(self, filename, hash_factory=hashlib.md5, chunk_num_blocks=128, i=1):
|
def fast_hash(self, filename: str, hash_factory: callable = hashlib.md5, chunk_num_blocks: int = 128, i: int = 1) -> str:
|
||||||
h = hash_factory()
|
h = hash_factory()
|
||||||
with open(filename,'rb') as f:
|
with open(filename,'rb') as f:
|
||||||
|
# NOTE: Jump to middle of file
|
||||||
f.seek(0, 2)
|
f.seek(0, 2)
|
||||||
mid = int(f.tell() / 2)
|
mid = int(f.tell() / 2)
|
||||||
f.seek(mid, 0)
|
f.seek(mid, 0)
|
||||||
|
|
||||||
while chunk := f.read(chunk_num_blocks*h.block_size):
|
while chunk := f.read(chunk_num_blocks * h.block_size):
|
||||||
h.update(chunk)
|
h.update(chunk)
|
||||||
if (i == 12):
|
if (i == 12):
|
||||||
break
|
break
|
||||||
|
|
|
@ -68,7 +68,7 @@ class IPCServer(Singleton):
|
||||||
def _handle_ipc_message(self, conn, start_time) -> None:
|
def _handle_ipc_message(self, conn, start_time) -> None:
|
||||||
while True:
|
while True:
|
||||||
msg = conn.recv()
|
msg = conn.recv()
|
||||||
if settings.is_debug():
|
if settings_manager.is_debug():
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
if "FILE|" in msg:
|
if "FILE|" in msg:
|
||||||
|
|
|
@ -35,7 +35,7 @@ class SettingsManager(StartCheckMixin, Singleton):
|
||||||
self._CSS_FILE = f"{self._HOME_CONFIG_PATH}/stylesheet.css"
|
self._CSS_FILE = f"{self._HOME_CONFIG_PATH}/stylesheet.css"
|
||||||
self._KEY_BINDINGS_FILE = f"{self._HOME_CONFIG_PATH}/key-bindings.json"
|
self._KEY_BINDINGS_FILE = f"{self._HOME_CONFIG_PATH}/key-bindings.json"
|
||||||
self._PID_FILE = f"{self._HOME_CONFIG_PATH}/{app_name.lower()}.pid"
|
self._PID_FILE = f"{self._HOME_CONFIG_PATH}/{app_name.lower()}.pid"
|
||||||
self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/icons/{app_name.lower()}.png"
|
self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png"
|
||||||
self._UI_WIDEGTS_PATH = f"{self._HOME_CONFIG_PATH}/ui_widgets"
|
self._UI_WIDEGTS_PATH = f"{self._HOME_CONFIG_PATH}/ui_widgets"
|
||||||
self._CONTEXT_MENU = f"{self._HOME_CONFIG_PATH}/contexct_menu.json"
|
self._CONTEXT_MENU = f"{self._HOME_CONFIG_PATH}/contexct_menu.json"
|
||||||
self._TRASH_FILES_PATH = f"{GLib.get_user_data_dir()}/Trash/files"
|
self._TRASH_FILES_PATH = f"{GLib.get_user_data_dir()}/Trash/files"
|
||||||
|
@ -92,6 +92,7 @@ class SettingsManager(StartCheckMixin, Singleton):
|
||||||
self._main_window_w = 1670
|
self._main_window_w = 1670
|
||||||
self._main_window_h = 830
|
self._main_window_h = 830
|
||||||
self._builder = None
|
self._builder = None
|
||||||
|
self.PAINT_BG_COLOR = (0, 0, 0, 0.0)
|
||||||
|
|
||||||
self._trace_debug = False
|
self._trace_debug = False
|
||||||
self._debug = False
|
self._debug = False
|
||||||
|
@ -127,6 +128,8 @@ class SettingsManager(StartCheckMixin, Singleton):
|
||||||
def get_main_window_width(self) -> Gtk.ApplicationWindow: return self._main_window_w
|
def get_main_window_width(self) -> Gtk.ApplicationWindow: return self._main_window_w
|
||||||
def get_main_window_height(self) -> Gtk.ApplicationWindow: return self._main_window_h
|
def get_main_window_height(self) -> Gtk.ApplicationWindow: return self._main_window_h
|
||||||
def get_builder(self) -> Gtk.Builder: return self._builder
|
def get_builder(self) -> Gtk.Builder: return self._builder
|
||||||
|
def get_paint_bg_color(self) -> list: return self.PAINT_BG_COLOR
|
||||||
|
|
||||||
def get_glade_file(self) -> str: return self._GLADE_FILE
|
def get_glade_file(self) -> str: return self._GLADE_FILE
|
||||||
def get_icon_theme(self) -> str: return self._ICON_THEME
|
def get_icon_theme(self) -> str: return self._ICON_THEME
|
||||||
def get_css_file(self) -> str: return self._CSS_FILE
|
def get_css_file(self) -> str: return self._CSS_FILE
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"base_of_home": "",
|
||||||
|
"hide_hidden_files": "true",
|
||||||
|
"thumbnailer_path": "ffmpegthumbnailer",
|
||||||
|
"blender_thumbnailer_path": "",
|
||||||
|
"go_past_home": "true",
|
||||||
|
"lock_folder": "false",
|
||||||
|
"locked_folders": "venv::::flasks",
|
||||||
|
"mplayer_options": "-quiet -really-quiet -xy 1600 -geometry 50%:50%",
|
||||||
|
"music_app": "deadbeef",
|
||||||
|
"media_app": "mpv",
|
||||||
|
"image_app": "mirage2",
|
||||||
|
"office_app": "libreoffice",
|
||||||
|
"pdf_app": "evince",
|
||||||
|
"code_app": "newton",
|
||||||
|
"text_app": "mousepad",
|
||||||
|
"terminal_app": "terminator",
|
||||||
|
"container_icon_wh": [128, 128],
|
||||||
|
"video_icon_wh": [128, 64],
|
||||||
|
"sys_icon_wh": [56, 56],
|
||||||
|
"file_manager_app": "solarfm",
|
||||||
|
"steam_cdn_url": "https://steamcdn-a.akamaihd.net/steam/apps/",
|
||||||
|
"remux_folder_max_disk_usage": "8589934592"
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"meshs": [".dae", ".fbx", ".gltf", ".obj", ".stl"],
|
||||||
|
"code": [".cpp", ".css", ".c", ".go", ".html", ".htm", ".java", ".js", ".json", ".lua", ".md", ".py", ".rs", ".toml", ".xml", ".pom"],
|
||||||
|
"videos": [".mkv", ".mp4", ".webm", ".avi", ".mov", ".m4v", ".mpg", ".mpeg", ".wmv", ".flv"],
|
||||||
|
"office": [".doc", ".docx", ".xls", ".xlsx", ".xlt", ".xltx", ".xlm", ".ppt", ".pptx", ".pps", ".ppsx", ".odt", ".rtf"],
|
||||||
|
"images": [".png", ".jpg", ".jpeg", ".gif", ".ico", ".tga", ".webp"],
|
||||||
|
"text": [".txt", ".text", ".sh", ".cfg", ".conf", ".log"],
|
||||||
|
"music": [".psf", ".mp3", ".ogg", ".flac", ".m4a"],
|
||||||
|
"pdf": [".pdf"]
|
||||||
|
},
|
||||||
|
"theming":{
|
||||||
|
"success_color": "#88cc27",
|
||||||
|
"warning_color": "#ffa800",
|
||||||
|
"error_color": "#ff0000"
|
||||||
|
},
|
||||||
|
"debugging": {
|
||||||
|
"ch_log_lvl": 20,
|
||||||
|
"fh_log_lvl": 10
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue