Skip to content
Snippets Groups Projects
xdgurl.py 15.5 KiB
Newer Older
akiraohgaki's avatar
akiraohgaki committed
#!/usr/bin/env python

akiraohgaki's avatar
akiraohgaki committed
"""xdgurl
An install helper program for desktop stuff.

akiraohgaki's avatar
akiraohgaki committed
Copyright: 2016, Akira Ohgaki
License: GPL-3+

https://github.com/xdgurl/xdgurl
akiraohgaki's avatar
akiraohgaki committed
"""

akiraohgaki's avatar
akiraohgaki committed
import sys
import os
import shutil
akiraohgaki's avatar
akiraohgaki committed
import json
import tempfile
import mimetypes
import subprocess

if sys.version_info.major >= 3:
akiraohgaki's avatar
akiraohgaki committed
    import urllib.request
    import urllib.error
    import urllib.parse
    import tkinter
akiraohgaki's avatar
akiraohgaki committed
    import tkinter.messagebox
akiraohgaki's avatar
akiraohgaki committed
    import urllib
    import urlparse
    import Tkinter
    import tkMessageBox
akiraohgaki's avatar
akiraohgaki committed

class XdgUrl:
    """Core class of xdgurl"""

    def __init__(self, xdg_url=''):
        self.xdg_url = xdg_url
        self.meta = self.parse()

        self.temp_dir = tempfile.gettempdir()
        self.home_dir = os.path.expanduser('~')
        #self.config_dir = os.path.join(self.home_dir, '.config', 'xdgurl')
        self.data_dir = os.path.join(self.home_dir, '.local', 'share')
        self.kde_data_dir = os.path.join(self.home_dir, '.kde', 'share')

        #self.config = {}

        self.destinations = {
            'downloads': os.path.join(self.home_dir, 'Downloads'),
            'documents': os.path.join(self.home_dir, 'Documents'),
            'pictures': os.path.join(self.home_dir, 'Pictures'),
            'music': os.path.join(self.home_dir, 'Music'),
            'videos': os.path.join(self.home_dir, 'Videos'),
            'wallpapers': os.path.join(self.data_dir, 'wallpapers'),
            'fonts': os.path.join(self.home_dir, '.fonts'),
            'cursors': os.path.join(self.home_dir, '.icons'),
            'icons': os.path.join(self.data_dir, 'icons'),
            'emoticons': os.path.join(self.data_dir, 'emoticons'),
            'themes': os.path.join(self.home_dir, '.themes'),
            'emerald_themes': os.path.join(self.home_dir, '.emerald', 'themes'),
            'enlightenment_themes': os.path.join(self.home_dir, '.e', 'e', 'themes'),
            'enlightenment_backgrounds': os.path.join(self.home_dir, '.e', 'e', 'backgrounds'),
            'fluxbox_styles': os.path.join(self.home_dir, '.fluxbox', 'styles'),
            'pekwm_themes': os.path.join(self.home_dir, '.pekwm', 'themes'),
            'icewm_themes': os.path.join(self.home_dir, '.icewm', 'themes'),
            'plasma_plasmoids': os.path.join(self.data_dir, 'plasma', 'plasmoids'),
            'plasma_look_and_feel': os.path.join(self.data_dir, 'plasma', 'look-and-feel'),
            'plasma_desktopthemes': os.path.join(self.data_dir, 'plasma', 'desktoptheme'),
            'kwin_effects': os.path.join(self.data_dir, 'kwin', 'effects'),
            'kwin_scripts': os.path.join(self.data_dir, 'kwin', 'scripts'),
            'kwin_tabbox': os.path.join(self.data_dir, 'kwin', 'tabbox'),
            'aurorae_themes': os.path.join(self.data_dir, 'aurorae', 'themes'),
            'dekorator_themes': os.path.join(self.data_dir, 'deKorator', 'themes'),
            'qtcurve': os.path.join(self.data_dir, 'QtCurve'),
            'color_schemes': os.path.join(self.data_dir, 'color-schemes'),
            'gnome_shell_extensions': os.path.join(self.data_dir, 'gnome-shell', 'extensions'),
            'cinnamon_applets': os.path.join(self.data_dir, 'cinnamon', 'applets'),
akiraohgaki's avatar
akiraohgaki committed
            'cinnamon_desklets': os.path.join(self.data_dir, 'cinnamon', 'desklets'),
            'cinnamon_extensions': os.path.join(self.data_dir, 'cinnamon', 'extensions'),
akiraohgaki's avatar
akiraohgaki committed
            'nautilus_scripts': os.path.join(self.data_dir, 'nautilus', 'scripts'),
            'amarok_scripts': os.path.join(self.kde_data_dir, 'apps', 'amarok', 'scripts'),
            'yakuake_skins': os.path.join(self.kde_data_dir, 'apps', 'yakuake', 'skins'),
            'cairo_clock_themes': os.path.join(self.home_dir, '.cairo-clock', 'themes')
        }
        self.destinations_alias = {
            'gnome_shell_themes': self.destinations['themes'],
            'cinnamon_themes': self.destinations['themes'],
            'gtk2_themes': self.destinations['themes'],
            'gtk3_themes': self.destinations['themes'],
            'metacity_themes': self.destinations['themes'],
            'xfwm4_themes': self.destinations['themes'],
            'openbox_themes': self.destinations['themes'],
            'kvantum_themes': self.destinations['themes'],
            'compiz_themes': self.destinations['emerald_themes'],
            'beryl_themes': self.destinations['emerald_themes'],
            'plasma4_plasmoids': self.destinations['plasma_plasmoids'],
            'plasma5_plasmoids': self.destinations['plasma_plasmoids'],
            'plasma5_look_and_feel': self.destinations['plasma_look_and_feel'],
            'plasma5_desktopthemes': self.destinations['plasma_desktopthemes'],
            'plasma_color_schemes': self.destinations['color_schemes']
        }
        self.destinations.update(self.destinations_alias)

        self.archive_types = {
            'tar': [
                'application/x-tar',
                'application/x-gzip',
                'application/gzip',
                'application/x-bzip',
                'application/x-bzip2',
                'application/x-xz',
                'application/x-lzma',
                'application/x-lzip',
                'application/x-compressed-tar',
                'application/x-bzip-compressed-tar',
                'application/x-bzip2-compressed-tar',
                'application/x-xz-compressed-tar',
                'application/x-lzma-compressed-tar',
                'application/x-lzip-compressed-tar'
            ],
            'zip': ['application/zip'],
            '7z': ['application/x-7z-compressed'],
            'rar': [
                'application/x-rar',
                'application/x-rar-compressed'
            ]
        }

        #self.config.update(self.read_config('config'))
        #self.destinations.update(self.read_config('destinations'))

    def read_config(self, name):
        path = os.path.join(self.config_dir, name + '.json')
        data = {}
        if os.path.isfile(path):
            f = open(path, 'r')
akiraohgaki's avatar
akiraohgaki committed
            data = json.load(f)
            f.close()
        return data

    def write_config(self, name, data):
        if not os.path.isdir(self.config_dir):
            os.makedirs(self.config_dir)
        path = os.path.join(self.config_dir, name + '.json')
        f = open(path, 'w')
        json.dump(data, f)
        f.close()

    def parse(self):
        meta = {
            'command': 'download',
            'url': '',
            'type': 'downloads',
            'filename': ''
        }

        if sys.version_info.major >= 3:
            parse_result = urllib.parse.urlparse(self.xdg_url)
            query = urllib.parse.parse_qs(parse_result.query)
        else:
            parse_result = urlparse.urlparse(self.xdg_url)
            query = urlparse.parse_qs(parse_result.query)
akiraohgaki's avatar
akiraohgaki committed

        if parse_result.netloc:
            meta['command'] = parse_result.netloc

        if 'url' in query and query['url'][0]:
            if sys.version_info.major >= 3:
                meta['url'] = urllib.parse.unquote(query['url'][0])
            else:
                meta['url'] = urllib.unquote(query['url'][0])
akiraohgaki's avatar
akiraohgaki committed

        if 'type' in query and query['type'][0]:
            if sys.version_info.major >= 3:
                meta['type'] = urllib.parse.unquote(query['type'][0])
            else:
                meta['type'] = urllib.unquote(query['type'][0])
akiraohgaki's avatar
akiraohgaki committed

        if 'filename' in query and query['filename'][0]:
            if sys.version_info.major >= 3:
                meta['filename'] = urllib.parse.unquote(query['filename'][0]).split('?')[0]
            else:
                meta['filename'] = urllib.unquote(query['filename'][0]).split('?')[0]
akiraohgaki's avatar
akiraohgaki committed

        if meta['url'] and not meta['filename']:
            meta['filename'] = os.path.basename(meta['url']).split('?')[0]
akiraohgaki's avatar
akiraohgaki committed

        return meta

    """
    def detect_desktop_environment(self):
        desktop_environment = 'unknown'
        if os.environ.get('KDE_FULL_SESSION') == 'true':
            desktop_environment = 'kde'
        elif os.environ.get('GNOME_DESKTOP_SESSION_ID'):
            desktop_environment = 'gnome'
        return desktop_environment
    """

    def install_plasmapkg(self, path, type='plasmoid'):
        status = subprocess.call(['plasmapkg2', '-t', type, '-i', path])
        if status == 0:
            return True
        return False

    def uncompress_archive(self, path, target_dir):
        (mimetype, encoding) = mimetypes.guess_type(path)
        status = None

        if mimetype in self.archive_types['tar']:
            status = subprocess.call(['tar', '-xf', path, '-C', target_dir])
        elif mimetype in self.archive_types['zip']:
            status = subprocess.call(['unzip', '-o', path, '-d', target_dir])
        elif mimetype in self.archive_types['7z']:
            # No space between -o and directory
            status = subprocess.call(['7z', 'x', path, '-o' + target_dir])
        elif mimetype in self.archive_types['rar']:
            status = subprocess.call(['unrar', 'e', path, target_dir])

        if status == 0:
            return True
        return False

    def download(self):
        url = self.meta['url']
        type = self.meta['type']
        filename = self.meta['filename']
        destination = self.destinations[type]

        temp_path = os.path.join(self.temp_dir, filename)
        path = os.path.join(destination, filename)

        print('Retrieving a file from ' + url)
        if sys.version_info.major >= 3:
            urllib.request.urlretrieve(url, temp_path)
        else:
            urllib.urlretrieve(url, temp_path)
akiraohgaki's avatar
akiraohgaki committed

        print('Creating a directory ' + destination)
        if not os.path.isdir(destination):
            os.makedirs(destination)

        print('Saving a file to ' + path)
        shutil.move(temp_path, path)
akiraohgaki's avatar
akiraohgaki committed

        print('Done')

    def install(self):
        url = self.meta['url']
        type = self.meta['type']
        filename = self.meta['filename']
        destination = self.destinations[type]

        temp_path = os.path.join(self.temp_dir, filename)
        path = os.path.join(destination, filename)

        print('Retrieving a file from ' + url)
        if sys.version_info.major >= 3:
            urllib.request.urlretrieve(url, temp_path)
        else:
            urllib.urlretrieve(url, temp_path)
akiraohgaki's avatar
akiraohgaki committed

        print('Creating a directory ' + destination)
        if not os.path.isdir(destination):
            os.makedirs(destination)

        print('Installing')
        if (type in ['plasma_plasmoids', 'plasma4_plasmoids', 'plasma5_plasmoids']
        and self.install_plasmapkg(temp_path, 'plasmoid')):
            print('The plasmoid has been installed')
            os.remove(temp_path)
        elif (type in ['plasma_look_and_feel', 'plasma5_look_and_feel']
        and self.install_plasmapkg(temp_path, 'lookandfeel')):
            print('The plasma look and feel has been installed')
            os.remove(temp_path)
        elif (type in ['plasma_desktopthemes', 'plasma5_desktopthemes']
        and self.install_plasmapkg(temp_path, 'theme')):
            print('The plasma desktop theme has been installed')
            os.remove(temp_path)
        elif (type == 'kwin_effects'
        and self.install_plasmapkg(temp_path, 'kwineffect')):
            print('The KWin effect has been installed')
            os.remove(temp_path)
        elif (type == 'kwin_scripts'
        and self.install_plasmapkg(temp_path, 'kwinscript')):
            print('The KWin script has been installed')
            os.remove(temp_path)
        elif (type == 'kwin_tabbox'
        and self.install_plasmapkg(temp_path, 'windowswitcher')):
            print('The KWin window switcher has been installed')
            os.remove(temp_path)
        elif self.uncompress_archive(temp_path, destination):
            print('The archive file has been uncompressed into ' + destination)
            os.remove(temp_path)
        else:
            print('Saving a file to ' + path)
            shutil.move(temp_path, path)
akiraohgaki's avatar
akiraohgaki committed

        print('Done')

    def execute(self):
akiraohgaki's avatar
akiraohgaki committed
        if (self.meta['command'] in ['download', 'install']
        and self.meta['url']
        and self.meta['type'] in self.destinations
        and self.meta['filename']):
            if self.meta['command'] == 'download':
                self.download()
            elif self.meta['command'] == 'install':
                self.install()
        else:
            raise Exception('Incorrect XDG-URL ' + self.xdg_url)
akiraohgaki's avatar
akiraohgaki committed

'''
class XdgUrlApp(Tkinter.Frame):
    """Confirmation dialog for xdgurl"""

    def __init__(self, master=None, core=None):
        Tkinter.Frame.__init__(self, master)
        self.pack()
        self.core = core
        self.master.title('xdgurl')
        self.master.geometry('300x120')
        self.create_widget()

    def create_widget(self):
        execute_text = 'Download'
        if self.core.meta['command'] == 'install':
            execute_text = 'Install'

        info_label = Tkinter.Label(self, text=execute_text + ': ' + self.core.meta['filename'])
        info_label.pack(padx=10, pady=5, anchor=Tkinter.W)

        message_label = Tkinter.Label(self, text='Do you want to continue?')
        message_label.pack(padx=10, pady=5, anchor=Tkinter.W)

        execute_button = Tkinter.Button(self, text=execute_text, command=self.execute)
        execute_button.pack(padx=5, pady=10, side=Tkinter.RIGHT)

        quit_button = Tkinter.Button(self, text='Cancel', command=self.quit)
        quit_button.pack(padx=5, pady=10, side=Tkinter.RIGHT)

    def execute(self):
        self.core.execute()
        sys.exit()

    def quit(self):
        sys.exit()
'''

akiraohgaki's avatar
akiraohgaki committed
def main():
akiraohgaki's avatar
akiraohgaki committed
    version = '1.0.0'

    parser = argparse.ArgumentParser(
        prog=program,
        description='An install helper program for desktop stuff',
        epilog='Check more information on https://github.com/xdgurl/xdgurl'
    )
    parser.add_argument(
        '-v', '--version',
        action='version',
        version='%(prog)s ' + version
    )
    parser.add_argument('xdg_url', help='XDG-URL')
    args = parser.parse_args()

    if args.xdg_url:
akiraohgaki's avatar
akiraohgaki committed
        """
        core = XdgUrl(args.xdg_url)
akiraohgaki's avatar
akiraohgaki committed
        window = Tkinter.Tk()
        app = XdgUrlApp(window, core)
        app.mainloop()
        """

        core = XdgUrl(args.xdg_url)
        if sys.version_info.major >= 3:
            window = tkinter.Tk()
        else:
            window = Tkinter.Tk()
akiraohgaki's avatar
akiraohgaki committed

        execute_text = 'Download'
        if core.meta['command'] == 'install':
            execute_text = 'Install'
        info_text = execute_text + ': ' + core.meta['filename'] + '\nFrom: ' + core.meta['url']

        if sys.version_info.major >= 3:
            confirm = tkinter.messagebox.askyesno(program, info_text + '\n\nDo you want to continue?')
        else:
            confirm = tkMessageBox.askyesno(program, info_text + '\n\nDo you want to continue?')

        if confirm:
akiraohgaki's avatar
akiraohgaki committed
            try:
                core.execute();
            except Exception as e:
akiraohgaki's avatar
akiraohgaki committed
                message = execute_text + ' failed\n' + str(e)
                if sys.version_info.major >= 3:
                    tkinter.messagebox.showerror(program, info_text + '\n\n' + message)
                else:
                    tkMessageBox.showerror(program, info_text + '\n\n' + message)
akiraohgaki's avatar
akiraohgaki committed
            else:
                message = execute_text + ' finished'
                print(message)
                if sys.version_info.major >= 3:
                    tkinter.messagebox.showinfo(program, info_text + '\n\n' + message)
                else:
                    tkMessageBox.showinfo(program, info_text + '\n\n' + message)
akiraohgaki's avatar
akiraohgaki committed
        sys.exit()
akiraohgaki's avatar
akiraohgaki committed

if __name__ == '__main__':
    main()