You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

452 lines
12 KiB

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""Youtubedlg module that contains util functions.
Attributes:
_RANDOM_OBJECT (object): Object that it's used as a default parameter.
YOUTUBEDL_BIN (string): Youtube-dl binary filename.
"""
from __future__ import unicode_literals
import os
import sys
import locale
import subprocess
from .info import __appname__
from .version import __version__
_RANDOM_OBJECT = object()
YOUTUBEDL_BIN = 'youtube-dl'
if os.name == 'nt':
YOUTUBEDL_BIN += '.exe'
def remove_shortcuts(path):
"""Return given path after removing the shortcuts. """
path = path.replace('~', os.path.expanduser('~'))
return path
def absolute_path(filename):
"""Return absolute path to the given file. """
path = os.path.realpath(os.path.abspath(filename))
return os.path.dirname(path).decode(get_encoding(), 'ignore')
def get_lib_path():
"""Return path to the current file. """
return os.path.dirname(__file__).decode(get_encoding(), 'ignore')
def open_dir(path):
"""Open path using default file navigator.
Return True if path exists else False. """
path = remove_shortcuts(path)
if not os.path.exists(path):
return False
if os.name == 'nt':
os.startfile(path)
else:
subprocess.call(('xdg-open', path))
return True
def encode_tuple(tuple_to_encode):
"""Turn size tuple into string. """
return '%s/%s' % (tuple_to_encode[0], tuple_to_encode[1])
def decode_tuple(encoded_tuple):
"""Turn tuple string back to tuple. """
s = encoded_tuple.split('/')
return int(s[0]), int(s[1])
def check_path(path):
"""Create path if not exist. """
if not os.path.exists(path):
os.makedirs(path)
def get_config_path():
"""Return user config path.
Note:
Windows = %AppData% + app_name
Linux = ~/.config + app_name
"""
if os.name == 'nt':
path = os.getenv('APPDATA')
else:
path = os.path.join(os.path.expanduser('~'), '.config')
return os.path.join(path, __appname__.lower())
def shutdown_sys(password=None):
"""Shuts down the system.
Returns True if no errors occur else False.
Args:
password (string): SUDO password for linux.
Note:
On Linux you need to provide sudo password if you don't
have elevated privileges.
"""
_stderr = subprocess.PIPE
_stdin = None
info = None
encoding = get_encoding()
if os.name == 'nt':
cmd = ['shutdown', '/s', '/t', '1']
# Hide subprocess window
info = subprocess.STARTUPINFO()
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
else:
if password:
_stdin = subprocess.PIPE
password = ('%s\n' % password).encode(encoding)
cmd = ['sudo', '-S', '/sbin/shutdown', '-h', 'now']
else:
cmd = ['/sbin/shutdown', '-h', 'now']
cmd = [item.encode(encoding, 'ignore') for item in cmd]
shutdown_proc = subprocess.Popen(cmd,
stderr=_stderr,
stdin=_stdin,
startupinfo=info)
output = shutdown_proc.communicate(password)[1]
return not output or output == "Password:"
def to_string(data):
"""Convert data to string.
Works for both Python2 & Python3. """
return '%s' % data
def get_time(seconds):
"""Convert given seconds to days, hours, minutes and seconds.
Args:
seconds (float): Time in seconds.
Returns:
Dictionary that contains the corresponding days, hours, minutes
and seconds of the given seconds.
"""
dtime = dict(seconds=0, minutes=0, hours=0, days=0)
dtime['days'] = int(seconds / 86400)
dtime['hours'] = int(seconds % 86400 / 3600)
dtime['minutes'] = int(seconds % 86400 % 3600 / 60)
dtime['seconds'] = int(seconds % 86400 % 3600 % 60)
return dtime
def get_encoding():
"""Return system encoding. """
try:
encoding = locale.getpreferredencoding()
'TEST'.encode(encoding)
except:
encoding = 'UTF-8'
return encoding
def get_locale_file():
"""Search for youtube-dlg locale file.
Returns:
The path to youtube-dlg locale file if exists else None.
Note:
Paths that get_locale_file() func searches.
__main__ dir, library dir, /usr/share/youtube-dlg/locale
"""
DIR_NAME = 'locale'
SEARCH_DIRS = [
os.path.join(absolute_path(sys.argv[0]), DIR_NAME),
os.path.join(get_lib_path(), DIR_NAME),
os.path.join('/usr', 'share', __appname__.lower(), DIR_NAME)
]
if sys.platform == 'darwin':
SEARCH_DIRS.append('/usr/local/Cellar/youtube-dl-gui/{version}/share/locale'.format(version=__version__))
for directory in SEARCH_DIRS:
if os.path.isdir(directory):
return directory
return None
def get_icon_file():
"""Search for youtube-dlg app icon.
Returns:
The path to youtube-dlg icon file if exists, else returns None.
Note:
Paths that get_icon_file() function searches.
__main__ dir, library dir, /usr/share/pixmaps, $XDG_DATA_DIRS
"""
SIZES = ('256x256', '128x128', '64x64', '48x48', '32x32', '16x16')
ICON_NAME = 'youtube-dl-gui_%s.png'
ICONS_LIST = [ICON_NAME % size for size in SIZES]
search_dirs = [
os.path.join(absolute_path(sys.argv[0]), 'icons'),
os.path.join(get_lib_path(), 'icons'),
]
# Append $XDG_DATA_DIRS on search_dirs
path = os.getenv('XDG_DATA_DIRS')
if path is not None:
for xdg_path in path.split(':'):
xdg_path = os.path.join(xdg_path, 'icons', 'hicolor')
for size in SIZES:
search_dirs.append(os.path.join(xdg_path, size, 'apps'))
# Also append /usr/share/pixmaps on search_dirs
search_dirs.append('/usr/share/pixmaps')
for directory in search_dirs:
for icon in ICONS_LIST:
icon_file = os.path.join(directory, icon)
if os.path.exists(icon_file):
return icon_file
return None
class TwoWayOrderedDict(dict):
"""Custom data structure which implements a two way ordrered dictionary.
TwoWayOrderedDict it's a custom dictionary in which you can get the
key:value relationship but you can also get the value:key relationship.
It also remembers the order in which the items were inserted and supports
almost all the features of the build-in dict.
Note:
Ways to create a new dictionary.
*) d = TwoWayOrderedDict(a=1, b=2) (Unordered)
*) d = TwoWayOrderedDict({'a': 1, 'b': 2}) (Unordered)
*) d = TwoWayOrderedDict([('a', 1), ('b', 2)]) (Ordered)
*) d = TwoWayOrderedDict(zip(['a', 'b', 'c'], [1, 2, 3])) (Ordered)
Examples:
>>> d = TwoWayOrderedDict(a=1, b=2)
>>> d['a']
1
>>> d[1]
'a'
>>> print d
TwoWayOrderedDict([('a', 1), ('b', 2)])
"""
_PREV = 0
_KEY = 1
_NEXT = 2
def __init__(self, *args, **kwargs):
self._items = item = []
self._items += [item, None, item] # Double linked list [prev, key, next]
self._items_map = {} # Map link list items into keys to speed up lookup
self._load(args, kwargs)
def __setitem__(self, key, value):
if key in self:
# If self[key] == key for example {'b': 'b'} and we
# do d['b'] = 2 then we dont want to remove the 'b'
# from our linked list because we will lose the order
if self[key] in self._items_map and key != self[key]:
self._remove_mapped_key(self[key])
dict.__delitem__(self, self[key])
if value in self:
# If value == key we dont have to remove the
# value from the items_map because the value is
# the key and we want to keep the key in our
# linked list in order to keep the order.
if value in self._items_map and key != value:
self._remove_mapped_key(value)
if self[value] in self._items_map:
self._remove_mapped_key(self[value])
# Check if self[value] is in the dict
# for cases like {'a': 'a'} where we
# have only one copy instead of {'a': 1, 1: 'a'}
if self[value] in self:
dict.__delitem__(self, self[value])
if key not in self._items_map:
last = self._items[self._PREV] # self._items prev always points to the last item
last[self._NEXT] = self._items[self._PREV] = self._items_map[key] = [last, key, self._items]
dict.__setitem__(self, key, value)
dict.__setitem__(self, value, key)
def __delitem__(self, key):
if self[key] in self._items_map:
self._remove_mapped_key(self[key])
if key in self._items_map:
self._remove_mapped_key(key)
dict.__delitem__(self, self[key])
# Check if key is in the dict
# for cases like {'a': 'a'} where we
# have only one copy instead of {'a': 1, 1: 'a'}
if key in self:
dict.__delitem__(self, key)
def __len__(self):
return len(self._items_map)
def __iter__(self):
curr = self._items[self._NEXT]
while curr is not self._items:
yield curr[self._KEY]
curr = curr[self._NEXT]
def __reversed__(self):
curr = self._items[self._PREV]
while curr is not self._items:
yield curr[self._KEY]
curr = curr[self._PREV]
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.items())
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.items() == other.items()
return False
def __ne__(self, other):
return not self == other
def _remove_mapped_key(self, key):
"""Remove the given key both from the linked list
and the map dictionary. """
prev, __, next = self._items_map.pop(key)
prev[self._NEXT] = next
next[self._PREV] = prev
def _load(self, args, kwargs):
"""Load items into our dictionary. """
for item in args:
if type(item) == dict:
item = item.iteritems()
for key, value in item:
self[key] = value
for key, value in kwargs.items():
self[key] = value
def items(self):
return [(key, self[key]) for key in self]
def values(self):
return [self[key] for key in self]
def keys(self):
return list(self)
def pop(self, key, default=_RANDOM_OBJECT):
try:
value = self[key]
del self[key]
except KeyError as error:
if default == _RANDOM_OBJECT:
raise error
value = default
return value
def popitem(self, last=True):
"""Remove and return a (key, value) pair from the dictionary.
If the dictionary is empty calling popitem() raises a KeyError.
Args:
last (bool): When False popitem() will remove the first item
from the list.
Note:
popitem() is useful to destructively iterate over a dictionary.
Raises:
KeyError
"""
if not self:
raise KeyError('popitem(): dictionary is empty')
if last:
__, key, __ = self._items[self._PREV]
else:
__, key, __ = self._items[self._NEXT]
value = self.pop(key)
return key, value
def update(self, *args, **kwargs):
self._load(args, kwargs)
def setdefault(self, key, default=None):
try:
return self[key]
except KeyError:
self[key] = default
return default
def copy(self):
return self.__class__(self.items())
def clear(self):
self._items = item = []
self._items += [item, None, item]
self._items_map = {}
dict.clear(self)