#!/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)