#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''GUI (PyQt4) Installer for Scratchbox1 based Maemo SDK'''
# This file is part of Maemo SDK
#
# Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
#
# Contact: juha-pekka.jokela@tieto.com
#
# 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 2 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 .
import os
import re
import sys
import pwd
import grp
import time
import socket
import urllib
import tempfile
import random
import signal
import traceback
import subprocess
from optparse import OptionParser
def Which(program):
'''Returns path to given binary, if it can be found.'''
def is_exe(fpath):
return os.path.exists(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
def install_package(package, askuser = True):
'''Tries to install specified debian package (or defined rpm equivalent), returns True on success'''
# Will ask user for confirmation, depending on "askuser" value
# Mappings for matching fedora / suse packages from debian package name
rpm_pkg_name = {'wget': 'wget', 'python-qt4': 'PyQt4', 'xserver-xephyr': 'xorg-x11-server-Xephyr'}
suse_pkg_name = {'wget': 'wget', 'python-qt4': 'python-qt4', 'xserver-xephyr': 'xorg-x11-server-extra'}
# Ask user, if requested
if askuser:
reply = raw_input('Do you want to install missing package "%s"? (Type "y" to install) ' % package)
else:
reply = 'y'
if reply.lower() in ['y', 'yes']:
if os.path.isfile('/etc/debian_version') and Which('apt-get'):
# Checking for /etc/debian_version will make installation to be tried on yum first, if
# user has f.ex. installed apt on fedora.
if askuser:
command = ['apt-get', 'install', package]
else:
command = ['apt-get', '-y', 'install', package]
elif Which('yum'):
if package in rpm_pkg_name:
if askuser:
command = ['yum', '-y', 'install', rpm_pkg_name[package]]
else:
command = ['yum', 'install', rpm_pkg_name[package]]
else:
print 'No fedora equivalent defined for debian package "%s", not installing' % package
return False
elif Which('zypper'):
if package in suse_pkg_name:
if askuser:
command = ['zypper', 'in', suse_pkg_name[package]]
else:
command = ['zypper', '-y', 'in', suse_pkg_name[package]]
else:
print 'No suse equivalent defined for debian package "%s", not installing' % package
return False
elif Which('apt-get'):
# apt-get found, but /etc/debian_version missing. Ask user what to do.
if askuser:
reply = raw_input('apt-get found, but /etc/debian_version is missing. Do you want to use'
'apt-get to install missing package %s?', package)
else:
reply = 'y'
if reply.lower() in ['y', 'yes']:
command = ['apt-get', 'install', package]
else:
return False
else:
print 'No compatible package manager found (only apt-get, yum and zypper are supported)'
return False
else:
return False
# Don't print to console if not in interactive mode
if askuser:
print 'Executing command:', command
p = subprocess.Popen(command)
p.wait()
if p.returncode:
print 'Automatic installation of package "%s" failed.' % command[2]
return False
return True
askedinstall = False
while (True):
try:
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4 import QtNetwork
except ImportError, e:
# Can raise AttributeError if the version of PyQt4 is prior to Qt 4.3,
# since according to
# http://doc.trolltech.com/4.4/porting4-overview.html#wizard-dialogs-qwizard
# QWizard and siblings were added to that version of Qt4. Systems that have
# too old Qt and bindings by default: Debian Etch.
print "Python Qt4 bindings are not found!"
if not askedinstall:
askedinstall = True
if install_package('python-qt4'):
print "Successfully installed package python-qt4"
else:
print "Failed to install Python Qt4 bindings"
sys.exit(1)
else:
break
try:
from PyQt4 import QtWebKit
except ImportError, e:
# Only WebKit is missing, if it was available, it would have been installed
# with Qt Python bindings package. Run installation without WebKit functionality.
HAVE_WEBKIT = False
else:
HAVE_WEBKIT = True
# wget needed by command line installers, make sure it exists. If not, ask if user
# wants to install it automatically.
if not Which('wget'):
print "wget not found!"
if not install_package('wget'):
sys.exit(1)
# Check once again to make sure it really does exist now
if not Which('wget'):
print "wget still missing, aborting installation."
sys.exit(1)
# command line options and arguments
OPTIONS = None
ARGS = None
OPT_PARSER = None
PROXY = dict()
# logger, initialized in main
LOG = None
XEPHYR_SHORTCUT_FILENAME = "start_xephyr.sh"
# names shown to the user (what is installed/removed and installer's name)
PRODUCT_NAME = 'Maemo %s SDK' % ("5.0")
PRODUCT_NAME_SHORT = 'SDK'
SB_NAME = 'Scratchbox'
MY_NAME = '%s Installer' % PRODUCT_NAME
MY_VERSION = "0.1.1 ($Revision: 2043 $)"
# SDK time & space consumption: these are very rough
INST_CONS_TIME = '20 minutes'
INST_CONS_SPACE = "3GB"
# where scratchbox is found
SB_PATH = "/scratchbox"
# scratchbox group
SB_GROUP = "sbox"
# window size
WINDOW_WIDTH = 720
WINDOW_HEIGHT = 540
USE_IMAGES = True
MAEMO_LINKS_FILENAME = "Maemo 5 links.html"
XEPHYR_DESKTOP_ENTRY_FILENAME = "xephyr.desktop"
# Base URL's
INSTALLER_BASE_URL = "http://repository.maemo.org/stable/5.0"
EULA_BASE_URL = "http://tablets-dev.nokia.com/eula"
# URL's of installers (can be set to local file)
SB_INSTALLER_URL = "%s/maemo-scratchbox-install_5.0.sh" % INSTALLER_BASE_URL
SDK_INSTALLER_URL = "%s/maemo-sdk-install_5.0.sh" % INSTALLER_BASE_URL
EULA_URL = "%s/index.php" % EULA_BASE_URL
TOKEN_URL = "%s/token.php" % EULA_BASE_URL
ALLOW_UPGRADE = True
# Install following packages (space separated) before apt-get dist-upgrade
UPDATE_INSTALL_PACKAGES = 'maemo-sdk-opt'
UPDATE_SCRIPT_FN = '/tmp/maemo-sdk-install-wizard_upgrade.sh'
IMAGES = ['http://tablets-dev.nokia.com/sdk_installer/applications.png',
'http://tablets-dev.nokia.com/sdk_installer/desktop.png',
'http://tablets-dev.nokia.com/sdk_installer/TaskSwitcher.png']
INSTALL_OPTIONS = [
["&Minimal rootstraps only", "none"],
["&Runtime environment", "maemo-sdk-runtime"],
["&Development environment", "maemo-sdk-dev"],
["Debu&g environment", "maemo-sdk-debug"]]
DEFAULT_INSTALL_OPTION = 2 # checked by default
BINPATHS = ["/usr/local/bin", "usr/bin"]
# return codes
RC_NO_ERROR = 0
RC_OUTOFDISKSPACE = 2
# default target names for this release of SDK, user can choose different
# prefix if default targets already exist in scratchbox
TARGET_PREFIX = "FREMANTLE"
TARGET_POSTFIX_X86 = "_X86"
TARGET_POSTFIX_ARMEL = "_ARMEL"
TARGET_X86 = TARGET_PREFIX + TARGET_POSTFIX_X86
TARGET_ARMEL = TARGET_PREFIX + TARGET_POSTFIX_ARMEL
# license text for the SDK (non-ASCII quotes replaced with ASCII)
SDK_LICENSE = \
'''1) IMPORTANT: READ CAREFULLY BEFORE INSTALLING, DOWNLOADING, OR USING THE
SOFTWARE DEVELOPMENT KIT ("SDK" AS DEFINED BELOW) AND/OR SOFTWARE INCLUDED INTO
THE SDK
2) The SDK comprises of a) some software copyrighted by Nokia Corporation or
third parties in binary form (collectively "Licensed Software") and/or b) Open
Source Software in binary and source code form.
3) Licensed Software (including, without limitation, the downloading,
installation and/or the use thereof) is subject to, and licensed to you under,
the Nokia Software Development Kit Agreement, which you have to accept if you
choose to download the Licensed Software. Licensed Software is distributed to
you only in binary form.
4) The SDK is provided to you "AS IS" and Nokia, its affiliates and/or its
licensors do not make any representations or warranties, express or implied,
including, without any limitation, the warranties of merchantability or
fittness for a particular purpose, or that the SDK will not infringe any any
third party patents, copyrights, trademarks or other rights, or that the SDK
will meet your requirements or that the operation of the SDK will be
uninterrupted and/or error-free. By downloading and/or using the SDK you accept
that installation, any use and the results of the installation and/or happens
and is solely at your own risk and that Nokia assumes no liability whatsoever
for any damages that you may incur or suffer in connection with the SDK and/or
the installation or use thereof.
5) The Open Source Software is licensed and distributed under the GNU General
Public License (GPL), the GNU lesser General Public License (LGPL, aka. The GNU
Library General Public License) and/or other copyright licenses, permissions,
notices or disclaimers containing obligation or permission to provide the
source code of such software with the binary / executable form delivery of the
said software. Any source code of such software that is not part of this
delivery is made available to you in accordance with the referred license terms
and conditions on http://www.maemo.org. Alternatively, Nokia offers to provide
any such source code to you on CD-ROM for a charge covering the cost of
performing such distribution, such as the cost of media, shipping and handling,
upon written request to Nokia at:
Source Code Requests
Nokia Corporation
P.O.Box 407
FIN00045 Nokia Group
Finland.
This offer is valid for a period of three (3) years.
The exact license terms of GPL, LGPL and said certain other licenses, as well
as the required copyright and other notices, permissions and acknowledgements
are reproduced in and delivered to you as part of the referred source code.
'''
class ImageHandler(object):
'''Class for handling images.'''
def __init__(self, imagelist):
'''Constructor'''
self.imagedata = None
self.imagelist = imagelist
def __del__(self):
'''Destructor'''
def downloadImage(self, i):
'''Download specified image, currently blocks untill finished'''
proxy = get_proxy('http')
try:
handle = urllib.urlopen(self.imagelist[i], proxies = proxy)
self.imagedata = handle.read()
except Exception, e:
print "Failed to load image:", e
self.imagedata = None
return False
else:
return True
def getImage(self):
'''Return image pixmap, if already loaded'''
if self.imagedata:
pixmap = QtGui.QPixmap()
pixmap.loadFromData(self.imagedata)
return pixmap
else:
return None
def get_all_usernames():
'''Returns non-system usernames if can get the system limits for normal
user's UID. If not then returns all of the usernames in the system. The
usernames are returned in a list.'''
user_conf_file = "/etc/adduser.conf"
# inclusive range of UIDs for normal (non-system) users
# by default this is the maximum possible range, that includes all users
first_uid = 0
last_uid = sys.maxint
found_first_uid = False
found_last_uid = False
# try to get the UID range
if os.path.isfile(user_conf_file):
for l in open(user_conf_file):
l = l.strip()
if l.startswith("FIRST_UID"):
first_uid = int(l.split("=")[1])
found_first_uid = True
if l.startswith("LAST_UID"):
last_uid = int(l.split("=")[1])
found_last_uid = True
# both found
if found_first_uid and found_last_uid:
break
usernames = []
for i in pwd.getpwall():
# if UID not within the range, then skip this user
if not first_uid <= i.pw_uid <= last_uid:
continue
usernames.append(i.pw_name)
usernames.sort()
return usernames
def get_default_username():
'''Returns username of the user that invoked sudo or su to run this
script. Returns None if couldn't get such entry.'''
# try to get the user that ran sudo
username = os.getenv('SUDO_USER')
# or user that ran su
if not username:
username = os.getenv('USERNAME')
if username:
# installing SDK as root is not allowed, also the list of users does
# not show system users
if username == 'root':
LOG("Omitting default user 'root'")
return None
# verify that this user actually exists
try:
pwd.getpwnam(username)
except KeyError:
LOG("User %s not in password file!" % username)
return None
return username
def xephyr_has_kb():
'''Tries to figure out whether we should use -kb with Xephyr'''
if Which('Xephyr'):
command = "Xephyr -help 2>&1 | grep '+kb'"
p = subprocess.Popen(command, shell = True, stdout=subprocess.PIPE)
stdout, stderr = p.communicate()
if stdout:
return True
return False
def get_scratchbox_users():
#Returns list of scratchbox users
userlist = []
userdir = '%s/users' % SB_PATH
#If user directory doesn't exist, there can be no users.
if os.path.isdir(userdir):
p = subprocess.Popen('ls %s' % userdir,
stdout = subprocess.PIPE,
shell = True)
userlist = p.communicate()[0].split("\n")
#Remove invalid entries, if any
for user in userlist:
if not os.path.isdir("%s/%s" % (userdir, user)):
userlist.remove(user)
#Remove last empty line
userlist.pop()
return userlist
def uninstall_scratchbox(has_64bit):
'''Tries to uninstall scratchbox from the system.'''
#Uninstallation needs to be performed differently on 64bit
if has_64bit:
packages_64bit = ['scratchbox-devkit-doctools',
'scratchbox-devkit-perl',
'scratchbox-devkit-debian',
'scratchbox-devkit-svn',
'scratchbox-devkit-git',
'scratchbox-devkit-apt-https',
'scratchbox-devkit-qemu',
'scratchbox-core']
for package in packages_64bit:
exec_cmd('dpkg --purge %s' % package)
else:
#Easier on 32bit
exec_cmd('apt-get remove -y --purge scratchbox-libs')
def get_user_targets(user):
'''Return list of targets for specified user'''
targets = []
preexec = lambda: set_guid(pwd.getpwnam(user),
True)
p = subprocess.Popen('%s/tools/bin/sb-conf list --targets' % SB_PATH,
stdout = subprocess.PIPE,
preexec_fn = preexec,
shell = True)
targets = p.communicate()[0].split("\n")
#Remove lines which do not point to valid directory (including errors / warnings)
for target in targets:
if not os.path.isdir("%s/users/%s/targets/%s" % (SB_PATH, user, target)):
targets.remove(target)
#Remove last empty line, as it's not removed by previous test
targets.pop()
return targets
def has_user_active_sessions(user):
'''Returns True if user has active scratchbox sessions'''
lines = 0
if os.path.isfile("%s/tools/bin/sb-conf" % SB_PATH):
p = subprocess.Popen('su %s -c "%s/tools/bin/sb-conf list --sessions"' % (user, SB_PATH),
stdout = subprocess.PIPE,
shell = True)
sessions = p.communicate()[0].split("\n")
for session in sessions:
if re.match("^/dev/pts/[0-9]+:\s+[0-9]+", session):
lines += 1
return lines > 1
def has_user_group(user, group):
'''Returns True if user already is part of specified group'''
'''group is checked from /etc/group instead of "groups" command'''
p = subprocess.Popen('grep %s /etc/group' % group,
stdout = subprocess.PIPE,
shell = True)
output = p.communicate()[0]
groupusers = output[output.rfind(':') + 1:len(output) - 1].split(',')
for groupuser in groupusers:
if groupuser == user:
return True
return False
def add_user_to_group(user, group):
'''Adds specified user to specified group'''
p = subprocess.Popen('usermod -a -G %s %s' % (group, user),
shell = True)
p.wait()
def remove_scratchbox_target(user, target):
'''Removes specified target from specified user, if it exists'''
#Doesn't try to remove targets, for which directory is not found, so
#error messages, and empty target strings will be ignored.
if target and os.path.isdir("%s/users/%s/targets/%s" % (SB_PATH, user, target)):
LOG("Removing target %s" % (target))
exec_cmd('%s/tools/bin/sb-conf remove -f %s' % (SB_PATH, target),
username = user,
set_sbox_gid = True)
def get_user_home_dir(user):
'''returns home directory for the specified user, or None'''
homedir = os.path.expanduser('~%s' % (user))
if os.path.isdir(homedir):
return homedir
else:
return None
def get_user_desktop_dir(user):
'''returns desktop directory for the specified user, or None'''
desktopdir = None
homedir = get_user_home_dir(user)
if homedir:
xdg_fn = "%s/.config/user-dirs.dirs" % (homedir)
if os.path.isfile(xdg_fn):
# See if we have XDB_DESKTOP_DIR entry in xdg's config file
xdg_file = open(xdg_fn, "r")
xdg_text = xdg_file.read().splitlines()
xdg_file.close()
for line in xdg_text:
line = line.strip()
if not line.startswith("#"):
index = line.find("XDG_DESKTOP_DIR")
if index is not -1:
splitline = line[index:].split("=")
desktopdir = splitline[1].strip("\" ")
desktopdir = desktopdir.replace("$HOME", homedir)
else:
# Not found, see if $HOME/Desktop exists, and use that instead.
if os.path.isdir("%s/Desktop" % (homedir)):
desktopdir = "%s/Desktop" % (homedir)
if desktopdir:
if os.path.isdir(desktopdir):
return desktopdir
else:
# Desktop directory not found, use home directory instead
if homedir:
return homedir
return None
def parse_options():
'''Parses the command line options'''
global OPTIONS
global ARGS
global OPT_PARSER
usage = 'Usage: %prog [options]' + \
'''
%s.
Installs %s.''' % (MY_NAME, PRODUCT_NAME)
OPT_PARSER = OptionParser(usage = usage, version = "%prog " + MY_VERSION)
OPT_PARSER.add_option("-a", dest="sourceslistfile",
help="Specify alternative sources.list file for both targets.", metavar="FILE")
(OPTIONS, ARGS) = OPT_PARSER.parse_args()
def set_proxy(proxy):
'''Sets http_proxy to specified'''
global PROXY
PROXY = dict([('http', proxy)])
def get_proxy(protocol):
'''returns proxy for specified protocol. On some systems PROXY['http'] can be None, and it can cause problems'''
global PROXY
proxy = None
if PROXY:
if protocol in PROXY:
if PROXY[protocol]:
proxy = PROXY
return proxy
def download_file(url, chown_username = None, set_exec_bit = True):
'''Downloads a file into temporary location. Returns that location.
url = URL to download from
chown_username = if set, the ownership of the downloaded file will be
given to this user
set_exec_bit = if True execute mode bit of the downloaded file will be
set'''
LOG("Downloading %s" % url)
filename = None
try:
split = os.path.splitext(url)
suf = split[len(split)-1]
proxy = get_proxy('http')
fd = tempfile.mkstemp(suffix = suf)
filehandle = os.fdopen(fd[0], "w")
filename = fd[1]
LOG("Filename:%s" % filename)
urlhandle = urllib.urlopen(url, proxies = proxy)
filehandle.write(urlhandle.read())
urlhandle.close()
filehandle.close()
except Exception, e:
LOG("Error downloading file %s: %s" % (url, e))
raise e
else:
LOG("Saved download into %s" % filename)
if set_exec_bit:
os.chmod(filename, 0700)
if chown_username:
pwd_ent = pwd.getpwnam(chown_username)
os.chown(filename, pwd_ent.pw_uid, pwd_ent.pw_gid)
return filename
def bool_to_yesno(bool_value):
'''Return yes if Boolean value is true, no if it is false'''
if bool_value:
return 'Yes'
else:
return 'No'
def file_append_lines(filename, lines):
'''Appends list of lines into a file. Newlines are added to each appended
line.
filename = name of the file
lines = list of lines'''
if not os.path.isfile(filename):
LOG("WARNING! Appending to non-existing file (%s), will be created!" %
(filename))
LOG("Appending into file %s lines %s" % (filename, lines))
fo = open(filename, "a")
try:
for line in lines:
fo.write(line + '\n')
finally:
fo.close()
def set_guid(pwd_ent, set_sbox_gid = False):
'''Changes the effective, real and saved set-user-ID user and group IDs,
should be used to permanently drop root privileges. Also sets HOME
environment variable since maemo-sdk command extensively uses that
variable.
pwd_ent = Password database entry of the user whose credentials will be
used
set_sbox_gid = The GID will be set to that of the sbox. This is required
to run the SDK installer and any other scratchbox related
command. sg cannot be used since it does not return the
exit status of the executed process. Another option is
newgrp command.'''
if set_sbox_gid:
gid = grp.getgrnam(SB_GROUP).gr_gid
else:
gid = pwd_ent.pw_gid
os.setgid(gid)
os.setuid(pwd_ent.pw_uid)
os.environ['HOME'] = pwd_ent.pw_dir
os.environ['USER'] = pwd_ent.pw_name # SDK installer needs this
class CmdExecError(Exception):
'''Exception raised when execution of a command fails.'''
pass
def exec_cmd(command, username = None, set_sbox_gid = False, send_text = None, donotwait = False, return_errorcode = False):
'''Executes a command and raises exception if command fails.
username = if set, will run command with the credentials (UID & GID) of
that user.
set_sbox_gid = sets the GID of executed command to sbox, so that scratchbox
commands can be executed. This is naturally only used when
the username is specified, since root is not allowed to run
scratchbox commands.
send_text = Text to send to stdin of the process before waiting for it
return_errorcode = return errorcode after processing'''
if username:
LOG("Executing as user %s: %s" % (username, command))
if set_sbox_gid:
#Check if user is already part of sbox group (in which case we don't have to use the newgrp hack)
p = subprocess.Popen('groups %s|grep %s' % (username, SB_GROUP),
stdout = subprocess.PIPE,
shell = True)
output = p.communicate()[0]
if not output:
#Use newgrp as user isn't part of sbox group yet
#First newgrp sets sbox as users default group, second one restores the original one.
LOG("User not part of %s group, using newgrp" % SB_GROUP)
command = "newgrp %s << 'END1'\nnewgrp << 'END2'\n%s\nEND2\nEND1\n" % (SB_GROUP, command)
#Execute as user with su
command = "su %s -c \"%s\"" % (username, command)
LOG("Final command:%s" % command)
if send_text:
p_stdin = subprocess.PIPE
else:
p_stdin = None # as default in Popen
p = subprocess.Popen(command,
stdin = p_stdin,
stdout = LOG.fo_log,
stderr = subprocess.STDOUT,
shell = True)
if send_text:
p.communicate(send_text)
if donotwait:
return
else:
p.wait()
if return_errorcode:
return p.returncode
if p.returncode:
raise CmdExecError("Giving up, because failed to: %s" % command)
def scratchbox_target_exists(username, target):
'''Returns True if specified target for specified username exists in
scratchbox.'''
return os.path.isdir("%s/users/%s/targets/%s" %
(SB_PATH, username, target))
def scratchbox_prefix_exist(username, prefix):
'''Returns True if either armel or x86 target with specified prefix exists
in scratchbox. The SDK installer creates targets by taking prefix and
appending architecture.'''
return scratchbox_target_exists(username, prefix + TARGET_POSTFIX_X86) or \
scratchbox_target_exists(username, prefix + TARGET_POSTFIX_ARMEL)
class Logger(object):
'''Class for logging.'''
def __init__(self):
'''Constructor'''
script_name = os.path.basename(sys.argv[0])
# file name of the log file: script name, with extenstion replaced
self.fn_log = "/tmp/%s.log" % script_name[:script_name.rfind('.')]
self.fo_log = None
try:
self.fo_log = open(self.fn_log, 'w')
except:
print ("Could not open log file %s!" % (self.fn_log,))
raise
self.log("Python version: %s" % repr(sys.version))
self.log("Installer version: %s" % MY_VERSION)
def __del__(self):
'''Destructor'''
if self.fo_log:
self.fo_log.close()
def log(self, msg):
"""Writes a log message."""
self.fo_log.write("V [%s]: %s\n" %
(time.strftime("%H:%M:%S %d.%m.%Y"), msg))
self.fo_log.flush()
def __call__(self, *args, **kwds):
'''Shortcut for log'''
self.log(*args, **kwds)
def log_exc(self):
'''Writes current exception information into the log file'''
fmt = '-' * 5 + " %s " + '-' * 5
self.log(fmt % "Begin logging exception")
traceback.print_exc(file = self.fo_log)
self.log(fmt % "End logging exception")
class HostInfo(object):
'''Information about host'''
def __init__(self):
'''Constructor'''
# whether host already has scratchbox installed
self.__has_scratchbox = os.path.isfile("%s/etc/scratchbox-version" %(SB_PATH))
self.__scratchbox_op_name = self.__get_scratchbox_op_name()
machines_64bit = ["x86_64"]
machine = os.uname()[4]
if machine in machines_64bit:
self.__has_64bit = True
else:
self.__has_64bit = False
if Which('Xephyr'):
self.__has_xephyr = True
else:
self.__has_xephyr = False
# Should we consider apt as our primary way of installing packages
if os.path.isfile('/etc/debian_version') and Which('apt-get'):
self.__has_apt_get = True
else:
self.__has_apt_get = False
if Which('yum'):
self.__has_yum = True
else:
self.__has_yum = False
if Which('zypper'):
self.__has_zypper = True
else:
self.__has_zypper = False
LOG("Got host info: %s" % self)
@property
def has_scratchbox(self):
return self.__has_scratchbox
@property
def has_xephyr(self):
return self.__has_xephyr
@property
def scratchbox_op_name(self):
return self.__scratchbox_op_name
@property
def has_64bit(self):
return self.__has_64bit
@property
def has_apt_get(self):
return self.__has_apt_get
@property
def has_yum(self):
return self.__has_yum
@property
def has_zypper(self):
return self.__has_zypper
def __get_scratchbox_op_name(self):
'''Returns string of what operation is needed to get scratchbox to this
host: install or upgrade'''
if self.has_scratchbox:
return "Upgrade"
else:
return "Install"
def __str__(self):
'''String representation method.'''
return str(self.__dict__)
# field names for wizard and its siblings
FNLicenseAccept = "license_accept" # bool
FNInstallSDK = "install_sdk" # bool
FNUpgradeSDK = "upgrade_sdk" # bool
FNInstallSB = "install_sb" # bool
FNUninstall = "uninstall" # bool
FNInstallNokiaApps = "install_nokia_apps" # bool
FNSelectedUsername = "selected_username" # string
FNTargetX86Exist = "target_x86_exist" # bool
FNTargetArmelExist = "target_armel_exist" # bool
FNRemoveTargets = "targets_remove" # bool
FNTargetPrefix = "targets_prefix" # string
FNSDKInstMOptArg = "sdk_m_opt_arg" # string
FNSDKInstMOptArgText = "sdk_m_opt_arg_txt" # string
FNInstallNokiaBins = "nokiabins_install" # bool
FNNokiaBinsRepo = "nokiabins_repo" # string
FNInstallXephyr = "install_xephyr" # bool
FNInstallXephyrShortcut = "install_xephyr_shortcut" # bool
FNInstallDesktopLinks = "install_desktop_links" # bool
FNEasyInstall = "easy_install" # bool
FNCreateSbHomeShortcut = "create_sb_home_shortcut" # bool
FNForceInstallLowSpace = "force_install_low_space" # bool
class ProxyPage(QtGui.QWizardPage):
'''Proxy settings page'''
def __init__(self):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
self.layout = QtGui.QVBoxLayout()
self.setLayout(self.layout)
def initializePage(self):
self.setCommitPage(True)
self.setTitle('Proxy Settings')
self.setSubTitle(" ")
proxy_host_port = QtGui.QWidget()
self.proxy_host = QtGui.QLineEdit()
txt = "Enter hostname for your proxy"
self.proxy_host.setToolTip(txt)
self.proxy_host.setWhatsThis(txt)
self.proxy_host_label = QtGui.QLabel("Proxy host:")
self.proxy_port = QtGui.QLineEdit()
txt = "Enter port number for your proxy"
self.proxy_port.setToolTip(txt)
self.proxy_port.setWhatsThis(txt)
intvalidator = QtGui.QIntValidator(1, 65535, self)
self.proxy_port.setValidator(intvalidator)
self.proxy_port_label = QtGui.QLabel("Proxy port:")
self.proxy_username = QtGui.QLineEdit()
txt = "Enter username for your proxy"
self.proxy_username.setToolTip(txt)
self.proxy_username.setWhatsThis(txt)
self.proxy_username_label = QtGui.QLabel("Proxy username:")
self.proxy_password = QtGui.QLineEdit()
txt = "Enter password for your proxy"
self.proxy_password.setToolTip(txt)
self.proxy_password.setWhatsThis(txt)
self.proxy_password.setEchoMode(QtGui.QLineEdit.Password)
self.proxy_password_label = QtGui.QLabel("Proxy password:")
self.login_enable = QtGui.QCheckBox("Show login")
self.proxy_button = QtGui.QPushButton("&Confirm")
self.connect(self.proxy_button,
QtCore.SIGNAL("clicked()"), self.confirmClicked)
self.connect(self.proxy_button,
QtCore.SIGNAL("clicked()"), self.confirmClicked)
self.connect(self.login_enable, QtCore.SIGNAL("toggled(bool)"),
self.loginEnableToggled)
proxy_host_port_layout = QtGui.QGridLayout()
proxy_host_port_layout.addWidget(self.proxy_host_label, 0, 0)
proxy_host_port_layout.addWidget(self.proxy_host, 0, 1)
proxy_host_port_layout.addWidget(self.proxy_port_label, 1, 0)
proxy_host_port_layout.addWidget(self.proxy_port, 1, 1)
proxy_host_port_layout.addWidget(self.proxy_username_label, 2, 0)
proxy_host_port_layout.addWidget(self.proxy_username, 2, 1)
proxy_host_port_layout.addWidget(self.proxy_password_label, 3, 0)
proxy_host_port_layout.addWidget(self.proxy_password, 3, 1)
proxy_host_port.setLayout(proxy_host_port_layout)
self.loginEnableToggled(False)
proxy_settings = QtGui.QWidget()
proxy_settings_layout = QtGui.QVBoxLayout()
proxy_settings_layout.addWidget(self.login_enable)
proxy_settings_layout.addWidget(proxy_host_port)
proxy_settings_layout.addWidget(self.proxy_button)
proxy_settings.setLayout(proxy_settings_layout)
self.layout.addWidget(proxy_settings)
self.notetext = QtGui.QLabel("Failed to connect, please check your proxy settings.")
self.notetext.setWordWrap(True)
self.notetext.setVisible(False)
self.layout.addWidget(self.notetext)
def loginEnableToggled(self, checked):
self.proxy_username_label.setVisible(checked)
self.proxy_username.setVisible(checked)
self.proxy_password_label.setVisible(checked)
self.proxy_password.setVisible(checked)
def confirmClicked(self):
host_str = str(self.proxy_host.text())
port_str = str(self.proxy_port.text())
if self.login_enable.isChecked():
user_str = str(self.proxy_username.text())
pswd_str = str(self.proxy_password.text())
if user_str and pswd_str:
loginstring = "%s:%s@" %(user_str, pswd_str)
else:
loginstring = ""
else:
loginstring = ""
if port_str != '' and host_str != '':
if host_str.startswith('http://'):
#Remove http:// from beginning, we will add it with login info
host_str = host_str[7:]
proxystring = 'http://%s%s:%s' % (loginstring, host_str, port_str)
os.environ['http_proxy'] = proxystring
set_proxy(proxystring)
else:
set_proxy(None)
os.environ['http_proxy'] = ('')
if self.wizard().imageHandler.downloadImage(random.randint(0, len(IMAGES)-1)):
self.wizard().next()
self.emit(QtCore.SIGNAL("completeChanged()"))
else:
self.notetext.setVisible(True)
def isComplete(self):
'''Installer will automatically move to next page, when connection works.'''
return False
class IntroPage(QtGui.QWizardPage):
'''Introduction page'''
def __init__(self):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
self.setTitle('%s Installation Wizard' % PRODUCT_NAME)
self.setSubTitle(" ")
intro_label = QtGui.QLabel("This installer will guide you through the steps "
"needed to install %s on your development "
"machine. The installation will take approximately %s "
"(depending on download speed) and about %s of disk space on system root. \n\n"
"It will install Scratchbox cross compilation environment togther with Maemo 5 "
"development files on your host system.\n" %
(PRODUCT_NAME, INST_CONS_TIME, INST_CONS_SPACE))
intro_label.setWordWrap(True)
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(intro_label)
def initializePage(self):
if USE_IMAGES:
pixmap = self.wizard().imageHandler.getImage()
if pixmap:
label = QtGui.QLabel()
label.setPixmap(pixmap)
label.setAlignment(QtCore.Qt.AlignCenter)
self.layout.addWidget(label)
self.setLayout(self.layout)
class LevelPage(QtGui.QWizardPage):
'''Introduction page - select standard or custom installation'''
def __init__(self, host_has_scratchbox, has_apt):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
self.setTitle('Install Mode')
self.setSubTitle(" ")
intro_label = QtGui.QLabel("Select install mode")
intro_label.setWordWrap(True)
self.button_group = QtGui.QButtonGroup()
self.btn_easy = QtGui.QRadioButton("&Standard installation")
txt = "Most options well be left for defaults, so there are less question " \
"asked during installation."
self.btn_easy.setToolTip(txt)
self.btn_easy.setWhatsThis(txt)
self.button_group.addButton(self.btn_easy, 0)
self.btn_custom = QtGui.QRadioButton("&Custom installation")
txt = "Allows you to select which parts to install."
self.btn_custom.setToolTip(txt)
self.btn_custom.setWhatsThis(txt)
self.button_group.addButton(self.btn_custom, 1)
self.btn_uninstall = QtGui.QRadioButton("&Uninstall")
txt = "Existing Scratchbox installation will be removed from the system."
self.btn_uninstall.setToolTip(txt)
self.btn_uninstall.setWhatsThis(txt)
self.button_group.addButton(self.btn_uninstall, 2)
if ALLOW_UPGRADE and host_has_scratchbox:
self.btn_custom.setChecked(True)
else:
self.btn_easy.setChecked(True)
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(intro_label)
self.layout.addWidget(self.btn_easy)
self.layout.addWidget(self.btn_custom)
if host_has_scratchbox and has_apt:
#uninstall currently supported only when installed from debian packages. Not offered otherwise.
self.layout.addWidget(self.btn_uninstall)
self.connect(self.btn_uninstall, QtCore.SIGNAL("toggled(bool)"),
self.uninstallToggled)
self.warning = QtGui.QLabel("Warning: %s and %s will be uninstalled "
"from the system. If there are any changes "
"in your %s home directory, it will be "
"preserved along with the desktop link if "
"there is one. Before going forward, make sure "
"you don't have any active logins." %
(SB_NAME, PRODUCT_NAME, SB_NAME))
self.warning.setWordWrap(True)
self.warning.setVisible(False)
self.layout.addWidget(self.warning)
self.registerField(FNEasyInstall, self.btn_easy)
self.registerField(FNUninstall, self.btn_uninstall)
self.setLayout(self.layout)
def uninstallToggled(self, on):
self.warning.setVisible(on)
def nextId(self):
'''Determines which page should be shown next'''
if self.btn_uninstall.isChecked():
return PageIdSummary
return PageIdLicense
class LicensePage(QtGui.QWizardPage):
'''EULA page'''
def __init__(self, has_64bit):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
self.setTitle('Open Source License Agreement')
self.setSubTitle(" ")
self.has_64bit = has_64bit
license_text_edit = QtGui.QTextEdit()
license_text_edit.setPlainText(SDK_LICENSE)
license_text_edit.setReadOnly(True)
txt = "%s License. Use Ctrl+Wheel to zoom the text." % \
(PRODUCT_NAME_SHORT)
license_text_edit.setToolTip(txt)
license_text_edit.setWhatsThis(txt)
accept_check_box = QtGui.QCheckBox("I &accept")
accept_check_box.setCheckState(QtCore.Qt.Unchecked)
self.registerField(FNLicenseAccept + '*', accept_check_box)
layout = QtGui.QVBoxLayout()
layout.addWidget(license_text_edit)
layout.addWidget(accept_check_box)
self.setLayout(layout)
def nextId(self):
'''Determines which page should be shown next'''
if self.field(FNEasyInstall).toBool():
# If couldn't figure out default username, ask user.
if not get_default_username():
return PageIdUsers
return PageIdNokiaBins
#Custom install
return PageIdUsers
class InstallOptsPage(QtGui.QWizardPage):
'''Installation options page'''
def __init__(self, host_has_scratchbox, scratchbox_op_name, has_xephyr, has_supported_package_manager):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
self.setTitle('Install Options')
self.setSubTitle(" ")
self.has_supported_package_manager = has_supported_package_manager
self.has_scratchbox = host_has_scratchbox
self.install_sb_checkbox = QtGui.QCheckBox(
scratchbox_op_name + " &" + SB_NAME)
self.install_sb_checkbox.setChecked(True)
if host_has_scratchbox:
info_str = ("A previous %s installation has been detected "
"in %s. You have an option to upgrade that "
"installation." % (SB_NAME, SB_PATH))
else:
info_str = ("Previous installation of %(sb_name)s was not "
"detected. This installer can only detect previous "
"installation of %(sb_name)s in %(sb_path)s. If you "
"have previously installed %(sb_name)s in some other "
"path, new version of %(sb_name)s will be installed "
"in %(sb_path)s by this installer." %
{ "sb_name" : SB_NAME, "sb_path" : SB_PATH})
info_label = QtGui.QLabel(info_str)
info_label.setWordWrap(True)
self.install_sdk_checkbox = QtGui.QCheckBox(
"Install &%s" % PRODUCT_NAME)
self.install_sdk_checkbox.setChecked(True)
self.upgrade_sdk_checkbox = QtGui.QCheckBox(
"Upgrade &%s" % PRODUCT_NAME)
self.upgrade_sdk_checkbox.setChecked(False)
self.upgrade_sdk_checkbox.hide()
self.registerField(FNInstallSB, self.install_sb_checkbox)
self.registerField(FNInstallSDK, self.install_sdk_checkbox)
self.registerField(FNUpgradeSDK, self.upgrade_sdk_checkbox)
self.connect(self.install_sb_checkbox, QtCore.SIGNAL("toggled(bool)"),
self, QtCore.SIGNAL("completeChanged()"))
self.connect(self.install_sdk_checkbox, QtCore.SIGNAL("toggled(bool)"),
self.sdkInstallCheckboxToggled)
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(info_label)
self.layout.addWidget(self.install_sb_checkbox)
self.layout.addWidget(self.install_sdk_checkbox)
self.xephyr_install_checkbox = QtGui.QCheckBox("Install Xephyr")
self.xephyr_desktop_checkbox = QtGui.QCheckBox("Add Xephyr desktop shortcut")
self.xephyr_desktop_checkbox.setChecked(True)
if not has_xephyr:
if self.has_supported_package_manager:
self.xephyr_install_checkbox.setChecked(True)
self.layout.addWidget(self.xephyr_install_checkbox)
else:
self.xephyr_install_checkbox.setChecked(False)
self.layout.addWidget(self.xephyr_desktop_checkbox)
self.sb_home_shortcut_checkbox = QtGui.QCheckBox("Add shortcut to Scratchbox home directory")
self.sb_home_shortcut_checkbox.setChecked(True)
self.layout.addWidget(self.sb_home_shortcut_checkbox)
self.desktop_links_checkbox = QtGui.QCheckBox("Add Maemo related links to Desktop")
self.desktop_links_checkbox.setChecked(True)
self.layout.addWidget(self.desktop_links_checkbox)
self.registerField(FNCreateSbHomeShortcut, self.sb_home_shortcut_checkbox)
self.registerField(FNInstallXephyr, self.xephyr_install_checkbox)
self.registerField(FNInstallXephyrShortcut, self.xephyr_desktop_checkbox)
self.registerField(FNInstallDesktopLinks, self.desktop_links_checkbox)
self.connect(self.xephyr_install_checkbox, QtCore.SIGNAL("toggled(bool)"),
self.installXephyrCheckboxToggled)
self.connect(self.xephyr_desktop_checkbox, QtCore.SIGNAL("toggled(bool)"),
self, QtCore.SIGNAL("completeChanged()"))
self.connect(self.sb_home_shortcut_checkbox, QtCore.SIGNAL("toggled(bool)"),
self, QtCore.SIGNAL("completeChanged()"))
self.connect(self.desktop_links_checkbox, QtCore.SIGNAL("toggled(bool)"),
self, QtCore.SIGNAL("completeChanged()"))
self.setLayout(self.layout)
def installXephyrCheckboxToggled(self, on):
if on:
self.xephyr_desktop_checkbox.setDisabled(False)
else:
self.xephyr_desktop_checkbox.setChecked(False)
self.xephyr_desktop_checkbox.setDisabled(True)
self.emit(QtCore.SIGNAL("completeChanged()"))
def initializePage(self):
'''Create list of detected upgradeable SDK targets'''
targets = []
if ALLOW_UPGRADE and self.has_scratchbox:
self.sdk_upgrade = QtGui.QWidget()
self.sdk_upgrade_layout = QtGui.QVBoxLayout(self.sdk_upgrade)
self.sdk_upgrade.setLayout(self.sdk_upgrade_layout)
self.sdk_upgrade.setDisabled(True)
self.do_sdk_upgrade = False
user = self.field(FNSelectedUsername).toString()
targets = self.getTargets(user)
if targets != []:
self.install_sdk_checkbox.setChecked(False)
info_str = ("Following scratchbox targets were detected. "
"Check those you want to upgrade. You cannot perform upgrade if doing SDK installation.")
info_label = QtGui.QLabel(info_str, self.sdk_upgrade)
info_label.setWordWrap(True)
self.sdk_upgrade_layout.addWidget(info_label)
for target in targets:
checkbox = QtGui.QCheckBox(target, self.sdk_upgrade)
self.sdk_upgrade_layout.addWidget(checkbox)
if self.isTargetUpgradeable(user, target):
self.connect(checkbox, QtCore.SIGNAL("toggled(bool)"), self, QtCore.SIGNAL("completeChanged()"))
checkbox.setChecked(True)
else:
checkbox.setDisabled(True)
else:
info_str = ("No upgradeable scratchbox targets detected")
info_label = QtGui.QLabel(info_str, self.sdk_upgrade)
info_label.setWordWrap(True)
self.sdk_upgrade_layout.addWidget(info_label)
self.layout.addWidget(self.sdk_upgrade)
def cleanupPage(self):
'''Remove current checkboxes if user presses back. We recreate new list with new user selected on previous page'''
if ALLOW_UPGRADE and self.has_scratchbox:
self.sdk_upgrade.deleteLater()
def sdkInstallCheckboxToggled(self, on):
if ALLOW_UPGRADE and self.has_scratchbox:
self.sdk_upgrade.setDisabled(on)
self.do_sdk_upgrade = not on
self.emit(QtCore.SIGNAL("completeChanged()"))
def isTargetUpgradeable(self, user, target):
sources = SB_PATH + '/users/' + user + '/targets/' + target + '/etc/apt/sources.list'
try:
sourcesfile = file(sources,'r')
except:
return False
lines = sourcesfile.readlines()
sourcesfile.close()
expression = re.compile(r" fremantle/")
for line in lines:
if expression.search(line):
return True
return False
def getTargets(self, user):
directory='%s/users/' % SB_PATH + user +'/targets'
dirs = [] #list of directories
if os.path.exists(directory):
"""Returns a list of directories."""
#list of directories and files
listing = os.listdir(directory)
#get just the directories
for direntry in listing:
if os.path.isdir(directory+os.sep+direntry) and direntry != 'links':
dirs.append(direntry)
return dirs
def validatePage(self):
if self.install_sdk_checkbox.isChecked():
#Install selected, not doing any upgrade
self.upgrade_sdk_checkbox.setChecked(False)
return True
if not self.has_scratchbox:
if self.install_sb_checkbox.isChecked():
return True
if ALLOW_UPGRADE and self.do_sdk_upgrade:
self.do_sdk_upgrade = False
user = self.field(FNSelectedUsername).toString()
pwd_ent = pwd.getpwnam(str(user))
upgradescript = '#!/bin/sh\n'
upgradescript +='# Automatically generated shell script to upgrade Nokia Maemo5 SDK targets\n'
upgradescript_fn = UPDATE_SCRIPT_FN
skipitems = 2
for child in self.sdk_upgrade.children():
if skipitems:
skipitems -= 1
else:
if child.isChecked():
#Target selected for upgrade, append it to our upgrade script
#create sources.list file for target, that contains only nokia repos
targetname = child.text()
sources = SB_PATH + '/users/' + user + '/targets/' + targetname + '/etc/apt/sources.list'
try:
sourcesfile = file(sources,'r')
except:
#Couldn't open file, skip to next selected target
continue
self.do_sdk_upgrade = True
lines = sourcesfile.readlines()
sourcesfile.close()
newsources_fn = '/tmp/maemo-sdk-install-wizard_' + targetname + '_sources.list'
expression = re.compile(r" fremantle/")
newsourcesfile = file(newsources_fn, 'wt')
newsourcesfile.write('# Automatically created by Nokia fremantle SDK installer\n')
for line in lines:
#Only add Nokia repositories to sources.list
if expression.search(line):
newsourcesfile.write(line)
newsourcesfile.write('\n')
newsourcesfile.close()
os.chmod(newsources_fn, 0777)
os.chown(newsources_fn, pwd_ent.pw_uid, pwd_ent.pw_gid)
self.do_sdk_upgrade = True
upgradescript += '\n# Upgrade target %s\n' % targetname
upgradescript += '%s/tools/bin/sb-conf select %s\n' % (SB_PATH, targetname)
upgradescript += 'if [ "$?" -ne "0" ]; then \n' \
' exit 1\n' \
'fi\n'
upgradescript += '%s/login fakeroot apt-get -o Dir::Etc::sourcelist="/tmp/maemo-sdk-install-wizard_%s_sources.list" update\n' % (SB_PATH, targetname)
upgradescript += 'if [ "$?" -ne "0" ]; then \n' \
' exit 1\n' \
'fi\n'
upgradescript += '%s/login fakeroot apt-get --force-yes -y install %s\n' % (SB_PATH, UPDATE_INSTALL_PACKAGES)
upgradescript += '%s/login fakeroot apt-get -o Dir::Etc::sourcelist="/tmp/maemo-sdk-install-wizard_%s_sources.list -o Acquire::Retries=5" ' \
'--force-yes -y dist-upgrade\n' % (SB_PATH, targetname)
upgradescript += 'if [ "$?" -ne "0" ]; then \n' \
' exit 1\n' \
'fi\n'
upgradescript += '%s/login fakeroot apt-get update\n\n' % (SB_PATH)
upgradescript += 'rm -f /tmp/maemo-sdk-install-wizard_%s_sources.list\n' % (targetname)
self.upgrade_sdk_checkbox.setChecked(self.do_sdk_upgrade)
if self.do_sdk_upgrade:
#At least one target set to be upgraded, create upgrade script
upgradescriptfile = file(upgradescript_fn, 'wt')
upgradescriptfile.write(upgradescript)
upgradescriptfile.write('rm %s\n' % upgradescript_fn)
upgradescriptfile.close()
os.chmod(upgradescript_fn, 0777)
os.chown(upgradescript_fn, pwd_ent.pw_uid, pwd_ent.pw_gid)
return True
if self.install_sb_checkbox.isChecked():
return True
return False
def isComplete(self):
'''Overrides the method of QWizardPage, to disable next button if
nothing is selected for installation.'''
if not self.has_scratchbox and not self.install_sb_checkbox.isChecked():
return False
if self.xephyr_install_checkbox.isChecked() or \
self.xephyr_desktop_checkbox.isChecked() or \
self.install_sb_checkbox.isChecked() or \
self.install_sdk_checkbox.isChecked() or \
self.sb_home_shortcut_checkbox.isChecked() or \
self.desktop_links_checkbox.isChecked():
return True
if self.has_scratchbox and ALLOW_UPGRADE:
#SDK upgrade selected
skipitems = 2
for child in self.sdk_upgrade.children():
# Skip non-checkbox items
if skipitems:
skipitems -= 1
else:
if child.isChecked():
return True
return False
def nextId(self):
'''Determines which page should be shown next'''
if self.field(FNUpgradeSDK).toBool():
#sources.list already correct, skip to summary
return PageIdSummary
if self.field(FNInstallSDK).toBool():
if self.field(FNTargetX86Exist).toBool() or self.field(FNTargetArmelExist).toBool():
return PageIdTargets
return PageIdPkg
#No SDK related operations, skip to summary page
return PageIdSummary
class UsersPage(QtGui.QWizardPage):
'''User selection page'''
def __init__(self, has_64bit):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
self.setTitle('User Selection')
self.setSubTitle(" ")
self.has_64bit = has_64bit
all_usernames = get_all_usernames()
default_username = get_default_username()
if not default_username: # in case default not found use first one
default_username = all_usernames[0]
users_list_widget = QtGui.QListWidget()
txt = ("List of users on the system. %s will be installed for the "
"selected user." % PRODUCT_NAME)
users_list_widget.setToolTip(txt)
users_list_widget.setWhatsThis(txt)
# will be set in following signal handler
self.__selected_username = None
self.__target_x86_exist = False # targets for selected user
self.__target_armel_exist = False
self.connect(users_list_widget,
QtCore.SIGNAL("currentTextChanged(const QString &)"),
self.selectedUsernameChanged)
# add usernames to the list
for index, username in enumerate(all_usernames):
users_list_widget.addItem(username)
if username == default_username:
users_list_widget.setCurrentRow(index)
self.registerField(FNSelectedUsername, self, "selectedUsername")
self.registerField(FNTargetX86Exist, self, "targetX86Exist")
self.registerField(FNTargetArmelExist, self, "targetArmelExist")
layout = QtGui.QVBoxLayout()
label = QtGui.QLabel("Install the SDK for the following user:")
layout.addWidget(label)
layout.addWidget(users_list_widget)
self.setLayout(layout)
def getSelectedUsername(self):
'''Returns selected username'''
return self.__selected_username
selectedUsername = QtCore.pyqtProperty("QString", getSelectedUsername)
targetX86Exist = QtCore.pyqtProperty(
"bool", lambda self: self.__target_x86_exist)
targetArmelExist = QtCore.pyqtProperty(
"bool", lambda self: self.__target_armel_exist)
def selectedUsernameChanged(self, new_name):
'''Signal handler for list widgets currentTextChanged. Sets the
selected username to the current selection. Also updates related
info.'''
# segmentation faults without cast
self.__selected_username = QtCore.QString(new_name)
self.__target_x86_exist = scratchbox_target_exists(
self.__selected_username, TARGET_X86)
self.__target_armel_exist = scratchbox_target_exists(
self.__selected_username, TARGET_ARMEL)
def nextId(self):
if self.field(FNEasyInstall).toBool():
return PageIdNokiaBins
#Custom install
return PageIdInstallOpts
class TargetsPage(QtGui.QWizardPage):
'''Targets page, shown only if targets exist for selected user.'''
def __init__(self):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
self.setTitle('Installation Targets')
self.setSubTitle(" ")
self.button_group = QtGui.QButtonGroup()
self.btn_remove = QtGui.QRadioButton("&Overwrite existing targets")
self.button_group.addButton(self.btn_remove, 0)
self.btn_new = QtGui.QRadioButton("Create &new targets")
self.button_group.addButton(self.btn_new, 1)
self.group_box = QtGui.QGroupBox(
"To &create new targets using different name prefix:")
info_str = ("Specify a prefix to be used for the new targets. "
"Ensure that it is not the same as any of the existing "
"%s targets (eg: %s). When the new targets are created, "
"the architecture (X86 or ARMEL) will be automatically added to the prefix." % \
(SB_NAME, TARGET_PREFIX))
info_label = QtGui.QLabel(info_str)
info_label.setWordWrap(True)
self.target_prefix_line_edit = QtGui.QLineEdit()
group_box_layout = QtGui.QVBoxLayout()
group_box_layout.addStretch()
group_box_layout.addWidget(info_label)
group_box_layout.addWidget(self.target_prefix_line_edit)
self.group_box.setLayout(group_box_layout)
self.connect(self.btn_remove,
QtCore.SIGNAL("toggled(bool)"),
self.removeTargetsCheckboxToggled)
self.btn_new.setChecked(True)
self.registerField(FNRemoveTargets, self.btn_remove)
self.registerField(FNTargetPrefix, self.target_prefix_line_edit)
layout = QtGui.QVBoxLayout()
self.label = QtGui.QLabel()
self.label.setWordWrap(True)
layout.addWidget(self.label)
layout.addWidget(self.btn_remove)
layout.addWidget(self.btn_new)
layout.addStretch()
layout.addWidget(self.group_box)
self.setLayout(layout)
def initializePage(self):
'''Overrides the method of QWizardPage, to initialize the page with
some dynamic text.'''
target_x86_exist = self.field(FNTargetX86Exist).toBool()
target_armel_exist = self.field(FNTargetArmelExist).toBool()
selected_username = self.field(FNSelectedUsername).toString()
assert target_armel_exist or target_x86_exist, \
"At least one target should exist for this page to be usable!"
# both exist
if target_x86_exist and target_armel_exist:
targets_str = "The targets %s and %s already exist for user %s." % \
(TARGET_X86, TARGET_ARMEL, selected_username)
# either one exist
else:
if target_x86_exist:
targets_str = "The target %s" % TARGET_X86
elif target_armel_exist:
targets_str = "The target %s" % TARGET_ARMEL
targets_str += " already exists for user %s." % selected_username
self.label.setText("%s Please choose appropriate action." %
(targets_str))
# set the line edits text to some non-existing target prefix
nonexistent_prefix = TARGET_PREFIX + time.strftime("_%Y%m%d")
while True:
if scratchbox_prefix_exist(selected_username, nonexistent_prefix):
nonexistent_prefix += "_"
else:
break
self.target_prefix_line_edit.setText(nonexistent_prefix)
def validatePage(self):
'''Overrides the method of QWizardPage, verify valid input from the
user'''
# not removing targets, verify that prefix is not used in sb
if not self.btn_remove.isChecked():
selected_username = self.field(FNSelectedUsername).toString()
# prefix exist
if scratchbox_prefix_exist(selected_username,
self.target_prefix_line_edit.text()):
QtGui.QMessageBox.critical(
self,
"Target exists!",
"A %s target with the prefix %s already exists for user %s. "
"Please specify a new prefix." %
(SB_NAME, self.target_prefix_line_edit.text(),
selected_username))
return False
return True
def removeTargetsCheckboxToggled(self, on):
'''Handler for toggled signal of remove_targets_checkbox . Enables
target name prefix group box if checkbox is unchecked, and disables the
group box otherwise. When the group box is enabled the focus is set to
line edit'''
self.group_box.setDisabled(on)
if not on: # group box is enabled
self.target_prefix_line_edit.setFocus()
class PkgPage(QtGui.QWizardPage):
'''SDK package selection page'''
def __init__(self):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
self.setTitle('Environment Selection')
self.setSubTitle(" ")
self.button_group = QtGui.QButtonGroup()
self.registerField(FNSDKInstMOptArg, self, "mOptArgument")
self.registerField(FNSDKInstMOptArgText, self, "mOptArgumentText")
layout = QtGui.QVBoxLayout()
label = QtGui.QLabel("Select the environment you wish to install.")
layout.addWidget(label)
# add all of the buttons
for index, item in enumerate(INSTALL_OPTIONS):
btn = QtGui.QRadioButton(item[0])
layout.addWidget(btn)
self.button_group.addButton(btn, index)
if index == DEFAULT_INSTALL_OPTION: # check the default button
btn.setChecked(True)
self.setLayout(layout)
def getMOptArgument(self):
'''Returns the argument of the -m option to be used with the SDK
installer according to current selection'''
return INSTALL_OPTIONS[self.button_group.checkedId()][1]
def getMOptArgumentText(self):
'''Returns the description of the -m option, with mnemonics removed'''
return INSTALL_OPTIONS[self.button_group.checkedId()][0].replace('&', '')
mOptArgument = QtCore.pyqtProperty(
"QString", getMOptArgument,
doc = "Argument to be passed to the -m option of the SDK installer")
mOptArgumentText = QtCore.pyqtProperty(
"QString", getMOptArgumentText,
doc = "Description (UI text) of argument to be passed to the -m "
"option of the SDK installer")
def nextId(self):
'''Determines which page should be shown next'''
if self.field(FNSDKInstMOptArg).toString() != INSTALL_OPTIONS[0][1]:
return PageIdNokiaBins
return PageIdSummary
class NokiaBinsPage(QtGui.QWizardPage):
'''Nokia Binaries page'''
def __init__(self):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
self.setTitle('Nokia Binaries')
label = QtGui.QLabel("Some of the Maemo APIs and Nokia applications are provided "
"by Nokia proprietary binary packages. In order to install them, "
"it is required to accept the End User License Agreement (EULA).")
label.setWordWrap(True)
self.setSubTitle(" ")
# Checkbox to toggle installation of Nokia Bins. Will also toggle visibility of
# components that require it.
self.nokia_bins_checkbox = QtGui.QCheckBox("Install Nokia &Bins")
txt = "Install Nokia Bins. Required for Maemo development."
self.nokia_bins_checkbox.setToolTip(txt)
self.nokia_bins_checkbox.setWhatsThis(txt)
# Contains all items that will be hidden when nokia bins checkbox is unchecked
self.nokia_bins_options = QtGui.QWidget()
nokia_bins_options_layout = QtGui.QVBoxLayout()
spacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Minimum,
QtGui.QSizePolicy.Expanding)
self.nokia_apps_checkbox = QtGui.QCheckBox(
"Install Nokia &Apps")
txt = "Install Nokia Apps. Requires Nokia Binaries."
self.nokia_apps_checkbox.setToolTip(txt)
self.nokia_apps_checkbox.setWhatsThis(txt)
self.nokia_apps_checkbox.setChecked(True)
self.registerField(FNInstallNokiaApps, self.nokia_apps_checkbox)
self.loadinglabel = QtGui.QLabel("Loading EULA page, this may take a while.")
self.loadinglabel.setWordWrap(True)
self.loadingprogress = QtGui.QProgressBar()
self.loadingprogress.setMinimum(0)
self.loadingprogress.setMaximum(0)
info_str = ('Please copy the Nokia Binaries sources.list entry from page below. '
'Then paste that entry into the edit box below and press confirm button')
self.info_label = QtGui.QLabel(info_str)
self.info_label.setWordWrap(True)
self.info_label.setOpenExternalLinks(True)
self.info_label.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse or
QtCore.Qt.LinksAccessibleByKeyboard)
self.info_label.hide()
self.timeout_timer = QtCore.QTimer()
self.timeout_timer.setSingleShot(True)
self.connect(self.timeout_timer, QtCore.SIGNAL("timeout()"), self.pageLoadTimeout)
# Create line edit widget for repository in case, automatic copying
# doesn't work on current PyQt version.
self.repo_line = QtGui.QLineEdit()
self.repo_line.setReadOnly(True)
self.repo_line.hide()
self.web = QtWebKit.QWebView()
self.webpage = QtWebKit.QWebPage()
self.confirm_button = QtGui.QPushButton("confirm")
self.confirm_button.hide()
self.connect(self.confirm_button,
QtCore.SIGNAL("clicked()"),
self, QtCore.SIGNAL("completeChanged()"))
self.webpage.setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks)
self.web.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding);
self.connect(self.webpage,
QtCore.SIGNAL("loadProgress(int)"),
self.loadProgress)
self.connect(self.webpage.mainFrame(),
QtCore.SIGNAL("urlChanged(QUrl)"),
self.urlChanged)
self.connect(self.webpage,
QtCore.SIGNAL("loadFinished(bool)"),
self.pageLoaded)
nokia_bins_options_layout.addWidget(self.nokia_apps_checkbox)
nokia_bins_options_layout.addWidget(self.loadinglabel)
nokia_bins_options_layout.addWidget(self.loadingprogress)
nokia_bins_options_layout.addItem(spacer)
nokia_bins_options_layout.addWidget(self.info_label)
nokia_bins_options_layout.addWidget(self.web)
nokia_bins_options_layout.addWidget(self.repo_line)
nokia_bins_options_layout.addWidget(self.confirm_button)
self.nokia_bins_options.setLayout(nokia_bins_options_layout)
self.nokia_bins_checkbox.setChecked(True)
self.connect(self.nokia_bins_checkbox,
QtCore.SIGNAL("toggled(bool)"),
self.groupBoxToggled)
self.connect(self.nokia_bins_checkbox, QtCore.SIGNAL("toggled(bool)"),
self, QtCore.SIGNAL("completeChanged()"))
self.registerField(FNInstallNokiaBins, self.nokia_bins_checkbox)
self.registerField(FNNokiaBinsRepo, self.repo_line)
layout = QtGui.QVBoxLayout()
layout.addWidget(label)
layout.addWidget(self.nokia_bins_checkbox)
layout.addWidget(self.nokia_bins_options)
self.setLayout(layout)
def initializePage(self):
if not self.isRepositoryValid():
#Repository is invalid, probably empty. Erase it just in case.
self.repo_line.setText("")
proxy = self.getSystemProxySettings()
if proxy != None:
self.webpage.networkAccessManager().setProxy(proxy)
#Show web page only if repository is not valid, otherwise
#we will get just install nokia bins & apps checkboxes.
self.loadEulaPage()
def loadEulaPage(self):
self.webpage.mainFrame().load(QtCore.QUrl(EULA_URL))
self.timeout_timer.start(90000)
def loadProgress(self, progress):
'''Update progress bar according to load progress'''
self.loadingprogress.setMaximum(100)
self.loadingprogress.setValue(progress)
def urlChanged(self, url):
'''Decides what to do when url changes'''
if url.toString() == TOKEN_URL:
self.web.hide()
self.connect(self.webpage,
QtCore.SIGNAL("loadFinished(bool)"),
self.getRepositoryLine)
else:
self.web.show()
def getRepositoryLine(self, ok):
'''Copies the repository line from the fully loaded web page'''
if ok:
if self.web.page().findText("deb "):
self.web.page().triggerAction(QtWebKit.QWebPage.SelectEndOfLine)
self.repo_line.setText(self.webpage.selectedText())
if self.isRepositoryValid():
self.emit(QtCore.SIGNAL("completeChanged()"))
self.wizard().next()
return
#Failed to get repository line automatically
self.info_label.show()
self.web.show()
self.confirm_button.show()
self.repo_line.setText("")
self.repo_line.setReadOnly(False)
self.repo_line.show()
def pageLoaded(self, ok):
'''Page loaded, stop timeout timer'''
self.timeout_timer.stop()
if ok:
#Loaded page successfully, show page & hide loading text
self.loadinglabel.hide()
self.loadingprogress.hide()
self.web.setPage(self.webpage)
else:
#Loading failed (or timeout), try reload
self.loadingprogress.setMinimum(0)
self.loadingprogress.setMaximum(0)
self.web.reload()
def pageLoadTimeout(self):
'''Executed when page isn't loaded in specified time'''
self.web.reload()
def groupBoxToggled(self, on):
'''Handler for group box's toggled signal. The goal is to focus on line
edit when group box is enabled. The focus is removed from disabled line
edit because if it has focus it steals all keyboard events, making
keyboard unusable. Also, if line edit is disabled the focus is moved to
the group box's checkbox, without this the focus would jump to the Next
button.'''
if on: # group box is enabled
self.nokia_apps_checkbox.show()
if not self.isRepositoryValid():
self.web.show()
self.loadEulaPage()
else:
self.web.hide()
self.nokia_apps_checkbox.hide()
def cleanupPage(self):
'''Overridden, not to clean-up the repo text edit as the default
implementation does.'''
return
def validatePage(self):
'''Overrides the method of QWizardPage, verify valid input from the
user'''
# make sure the specified repo seems valid
if self.nokia_bins_checkbox.isChecked():
if not self.isRepositoryValid():
QtGui.QMessageBox.critical(
self,
"Invalid repository!",
"Specified repository is invalid, please provide valid "\
"one!",
QtGui.QMessageBox.Ok)
return False
else:
self.nokia_apps_checkbox.setChecked(False)
return True
def isRepositoryValid(self):
'''make sure the specified repo is close to valid'''
repo = str(self.repo_line.text())
pat = r"deb .* nokia-binaries$"
if re.match(pat, repo):
return True
return False
def getSystemProxySettings(self):
'''Returns QNetworkProxy for WebKit based on system proxy settings'''
proxy = get_proxy('http')
if proxy:
http_proxy = proxy['http']
username_password = []
index = http_proxy.find('@')
if index != -1:
# We have username & password
split_proxy = http_proxy.split('@')
if len(split_proxy) == 2:
# We have username specified
if split_proxy[0].lower().startswith('http://'):
username_password = split_proxy[0][7:].split(':')
host_port_str = split_proxy[1]
else:
return None
else:
if http_proxy.lower().startswith('http://'):
host_port_str = http_proxy[7:]
else:
return None
host_port = host_port_str.split(':')
if len(host_port) == 2:
port = host_port[1].rstrip('/ ')
try:
port_num = int(port)
except:
return None
proxy = QtNetwork.QNetworkProxy()
proxy.setHostName(host_port[0])
proxy.setPort(port_num)
proxy.setType(QtNetwork.QNetworkProxy.HttpProxy)
if username_password != []:
proxy.setUser(username_password[0])
proxy.setPassword(username_password[1])
return proxy
return None
def isComplete(self):
'''Overrides the method of QWizardPage, to disable next button if
installation is selected, but repository is not valid.'''
if self.nokia_bins_checkbox.isChecked():
return self.isRepositoryValid()
else:
return True
class NokiaBinsPageNoWebkit(QtGui.QWizardPage):
'''Nokia Binaries page'''
def __init__(self):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
self.setTitle('Nokia Binaries')
self.setSubTitle(" ")
label = QtGui.QLabel("Some of the Maemo APIs and Nokia applications are provided "
"by Nokia proprietary binary packages. In order to install them, "
"it is required to accept the End User License Agreement (EULA).")
label.setWordWrap(True)
self.nokia_bins_checkbox = QtGui.QCheckBox(
"Install Nokia &Bins")
txt = "Install Nokia Bins. Required for Maemo development."
self.nokia_bins_checkbox.setToolTip(txt)
self.nokia_bins_checkbox.setWhatsThis(txt)
# Contains all items that will be hidden when nokia bins checkbox is unchecked
self.nokia_bins_options = QtGui.QWidget()
nokia_bins_options_layout = QtGui.QVBoxLayout()
self.nokia_apps_checkbox = QtGui.QCheckBox("&Install Nokia Apps")
self.nokia_apps_checkbox.setChecked(True)
self.registerField(FNInstallNokiaApps, self.nokia_apps_checkbox)
info_str = ('Please visit this '
'webpage to '
'obtain Nokia Binaries sources.list entry. Then paste that entry into '
'the edit box below in order for Nokia Binaries to be installed into '
'targets by this installer' % (EULA_URL,))
info_label = QtGui.QLabel(info_str)
info_label.setWordWrap(True)
info_label.setOpenExternalLinks(True)
info_label.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse or
QtCore.Qt.LinksAccessibleByKeyboard)
txt = "Use link to visit %s to get sources.list entry" % EULA_URL
info_label.setToolTip(txt)
info_label.setWhatsThis(txt)
self.repo_line_edit = QtGui.QLineEdit()
nokia_bins_options_layout.addWidget(self.nokia_apps_checkbox)
nokia_bins_options_layout.addWidget(info_label)
nokia_bins_options_layout.addWidget(self.repo_line_edit)
self.nokia_bins_options.setLayout(nokia_bins_options_layout)
self.nokia_bins_checkbox.setChecked(True)
self.connect(self.nokia_bins_checkbox,
QtCore.SIGNAL("toggled(bool)"),
self.groupBoxToggled)
self.registerField(FNInstallNokiaBins, self.nokia_bins_checkbox)
self.registerField(FNNokiaBinsRepo, self.repo_line_edit)
layout = QtGui.QVBoxLayout()
layout.addWidget(label)
layout.addWidget(self.nokia_bins_checkbox)
layout.addWidget(self.nokia_bins_options)
layout.addStretch()
self.setLayout(layout)
def groupBoxToggled(self, on):
'''Handler for group box's toggled signal. The goal is to focus on line
edit when group box is enabled. The focus is removed from disabled line
edit because if it has focus it steals all keyboard events, making
keyboard unusable. Also, if line edit is disabled the focus is moved to
the group box's checkbox, without this the focus would jump to the Next
button.'''
if on: # group box is enabled
self.nokia_bins_options.setFocusProxy(self.repo_line_edit)
self.nokia_bins_options.show()
else:
self.nokia_bins_options.setFocusProxy(None)
self.nokia_bins_options.hide()
self.nokia_bins_options.setFocus()
def cleanupPage(self):
'''Overridden, not to clean-up the repo text edit as the default
implementation does.'''
return
def validatePage(self):
'''Overrides the method of QWizardPage, verify valid input from the
user'''
# make sure the specified repo is close to valid
if self.nokia_bins_checkbox.isChecked():
repo = str(self.repo_line_edit.text())
pat = r"deb .* nokia-binaries$"
m = re.match(pat, repo)
if not m:
QtGui.QMessageBox.critical(
self,
"Invalid repository!",
"Specified repository is invalid, please provide valid "\
"one!",
QtGui.QMessageBox.Ok)
return False
else:
self.nokia_apps_checkbox.setChecked(False)
return True
class SummaryPage(QtGui.QWizardPage):
'''Summary page'''
def __init__(self):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
self.setTitle('Summary')
self.setSubTitle(" ")
# pages after this will have back button disabled
self.setCommitPage(True)
self.summary_label = QtGui.QLabel()
self.summary_label.setWordWrap(True)
self.summary_label.setAlignment(QtCore.Qt.AlignLeft |
QtCore.Qt.AlignTop)
# scroll area is used to add a scrolling capability in case the
# text in the label becomes too long
scroll_area = QtGui.QScrollArea(self)
scroll_area.setWidgetResizable(True)
scroll_area.setFrameStyle(QtGui.QFrame.NoFrame)
scroll_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
# widget placed in scroll area
sa_widget = QtGui.QWidget()
# layout for the widget
sa_widget_layout = QtGui.QVBoxLayout(sa_widget)
sa_widget_layout.addWidget(self.summary_label)
scroll_area.setWidget(sa_widget)
# layout for the page
layout = QtGui.QVBoxLayout()
layout.addWidget(scroll_area)
self.warning_label = QtGui.QLabel()
self.warning_label.setWordWrap(True)
self.warning_label.setVisible(False)
layout.addWidget(self.warning_label)
self.warning_checkbox = QtGui.QCheckBox("&Install anyway")
self.warning_checkbox.setVisible(False)
self.registerField(FNForceInstallLowSpace + '*', self.warning_checkbox)
layout.addWidget(self.warning_checkbox)
self.setLayout(layout)
def initializePage(self):
'''Overrides the method of QWizardPage, to initialize the page with
summary text.'''
summary = ""
space_needed = 0
# create the summary text
if self.field(FNUninstall).toBool():
self.wizard().setButtonText(QtGui.QWizard.CommitButton,
"&Uninstall")
summary += "Uninstall %s: Yes
" % SB_NAME
install_sb = False
install_sdk = False
else:
self.wizard().setButtonText(QtGui.QWizard.CommitButton,
"&Install")
# scratchbox
install_sb = self.field(FNInstallSB).toBool()
if install_sb:
summary += "%s %s: %s
" % \
(self.wizard().hostInfo.scratchbox_op_name, SB_NAME,
bool_to_yesno(self.field(FNInstallSB).toBool()))
# user
summary += "Username: %s
" % \
(self.field(FNSelectedUsername).toString())
# SDK
install_sdk = self.field(FNInstallSDK).toBool()
upgrade_sdk = self.field(FNUpgradeSDK).toBool()
install_desktop_links = self.field(FNInstallDesktopLinks).toBool()
summary += "Install %s: %s
" % \
(PRODUCT_NAME, bool_to_yesno(install_sdk))
# these are only available if SDK is installed
if install_sdk:
# targets
target_x86_exist = self.field(FNTargetX86Exist).toBool()
target_armel_exist = self.field(FNTargetArmelExist).toBool()
if target_x86_exist or target_armel_exist: # they exist
# remove targets
remove_targets = self.field(FNRemoveTargets).toBool() or self.field(FNEasyInstall).toBool()
summary += "Overwrite targets: %s
" % \
(bool_to_yesno(remove_targets))
# target name prefix
if not remove_targets:
summary += "Target name prefix: %s
" % \
(self.field(FNTargetPrefix).toString())
# packages
summary += "Packages to install: %s
" % \
(self.field(FNSDKInstMOptArgText).toString())
# Nokia Binaries
install_nokia_bins = self.field(FNInstallNokiaBins).toBool() and \
self.field(FNSDKInstMOptArg).toString() != INSTALL_OPTIONS[0][1]
summary += "Install Nokia Binaries: %s
" % \
(bool_to_yesno(install_nokia_bins))
# Nokia Apps
if install_sdk or upgrade_sdk:
install_nokia_apps = self.field(FNInstallNokiaApps).toBool()
summary += "Install Nokia Apps: %s
" % bool_to_yesno(install_nokia_apps and
self.field(FNSDKInstMOptArg).toString() != INSTALL_OPTIONS[0][1])
if upgrade_sdk:
summary += "Upgrade %s: %s
" % \
(PRODUCT_NAME, bool_to_yesno(upgrade_sdk))
if self.field(FNInstallXephyr).toBool():
summary += "Install Xephyr: Yes
"
summary += "Install Xephyr desktop shortcut: %s
" % \
(bool_to_yesno(self.field(FNInstallXephyrShortcut).toBool()))
summary += "Install Scratchbox home directory shortcut: %s
" % \
(bool_to_yesno(self.field(FNCreateSbHomeShortcut).toBool()))
summary += "Install Maemo-related Desktop links: %s
" % \
(bool_to_yesno(install_desktop_links))
# Calculate approximate needed space
if self.field(FNInstallXephyr).toBool() or \
self.field(FNInstallXephyrShortcut).toBool() or \
self.field(FNCreateSbHomeShortcut) or \
install_desktop_links:
space_needed += 5
# Don't add to space needed, if we are installing scratchbox on top of existing installation
if install_sb and not self.wizard().hostInfo.has_scratchbox:
space_needed += 2100
if install_sdk:
space_needed += 900
summary += "Estimated size (MB): %d
" % space_needed
# yes, I hate manual work
dont_want_end = "
"
if summary.endswith(dont_want_end):
summary = summary[:-len(dont_want_end)]
self.summary_label.setText(summary)
# Check if /scratchbox exists first, as it might be a symlink to another partition
if os.path.exists(SB_PATH):
stat = os.statvfs(SB_PATH)
else:
# Not found, check space in root, as that's where we are going to create the dir
stat = os.statvfs('/')
free_megs = stat.f_bavail*stat.f_bsize/1024/1024
# Show warning if user has less space than recommended
if free_megs < space_needed:
self.warning_label.setText("Warning: %d megabytes of free space detected, "
"while at least %s is suggested for full scratchbox & "
"SDK installation, are you sure you want to continue?
"
"Estimate is rounded up, and doesn't take into account "
"any existing installation you might be overwriting." %
(free_megs, space_needed))
self.warning_label.setVisible(True)
self.warning_checkbox.setVisible(True)
self.warning_checkbox.setChecked(False)
else:
self.warning_label.setVisible(True)
self.warning_checkbox.setVisible(False)
self.warning_checkbox.setChecked(True)
class ExecutorThread(QtCore.QThread):
'''Thread that does the actual installation. This is not done in the GUI
thread not to freeze the GUI.
Because in GUI applications, the main thread (a.k.a. the GUI thread) is the
only thread that is allowed to perform GUI-related operations, signals are
sent from this thread to the main thread to give some visual feedback of
completed operations.'''
# thread exit status
ExitStatusNotStarted = "status_not_started"
ExitStatusOK = "status_ok"
ExitStatusAborted = "status_aborted"
ExitStatusError = "status_error"
def __init__(self, host_info, uninstall, install_scratchbox, install_sdk, upgrade_sdk, install_apps,
sdk_inst_m_opt_arg, selected_username, targets_exist,
remove_targets, target_prefix,
install_nokia_bins, nokia_bins_repo, install_xephyr, install_xephyr_icon,
install_sb_home_shortcut, install_desktop_links):
'''Constructor'''
QtCore.QThread.__init__(self)
self.__host_info = host_info
self.__uninstall = uninstall
self.__install_scratchbox = install_scratchbox
self.__install_sdk = install_sdk
self.__upgrade_sdk = upgrade_sdk
self.__install_apps = install_apps
self.__sdk_inst_m_opt_arg = sdk_inst_m_opt_arg
self.__selected_username = selected_username
self.__targets_exist = targets_exist
self.__remove_targets = remove_targets
self.__target_prefix = target_prefix
self.__install_nokia_bins = install_nokia_bins and (self.__sdk_inst_m_opt_arg != INSTALL_OPTIONS[0][1])
self.__nokia_bins_repo = nokia_bins_repo
self.__install_xephyr = install_xephyr
self.__install_xephyr_icon = install_xephyr_icon
self.__install_sb_home_shortcut = install_sb_home_shortcut
self.__install_desktop_links = install_desktop_links
# for progress indication (a task can have many ops)
self.__ops_total_num = self.__calcOpsTotalNum()
self.__ops_done_num = 0 # number of done so far
# abort installation after current task is completed
self.__abort = False
self.__is_running_last_task = False
self.__exit_status = self.__class__.ExitStatusNotStarted
# signals emitted when new installation operation started/ended (the
# GUI thread uses these signals to update the progress bar and text on
# a label)
self.sig_op_started = QtCore.SIGNAL("opStarted")
self.sig_op_ended = QtCore.SIGNAL("opEnded")
LOG("Executor created: %s" % self)
opsTotalNum = property(lambda self: self.__ops_total_num)
exitStatus = property(lambda self: self.__exit_status)
def abort(self):
'''Called by the main thread to stop this thread after current
installation operation completes. If the thread is running last task,
the request to abort will be ignored, since the installation will end
after that task anyway.
It is possible to have the abort dialog shown in the beginning while
the executor will proceed to run last task, in that case the user will
have "Abort later" button visible even though last task is executed.'''
if not self.isRunningLastTask():
LOG("Executor accepted request to abort")
self.__abort = True
else:
LOG("Executor ignoring abort request since running last task")
def isAborting(self):
'''Whether this thread is about to abort the installation.'''
return self.__abort
def isRunningLastTask(self):
'''Whether this thread is executing last task.'''
return self.__is_running_last_task
def __str__(self):
'''String representation method.'''
return str(self.__dict__)
def __calcOpsTotalNum(self):
'''Returns number of total operations to complete the
installation. Each task can have many operations.'''
count = 0
if self.__uninstall:
count += 3
else:
if self.__install_scratchbox:
count += 2
if self.__install_sdk:
count += 2
if self.__upgrade_sdk:
count += 1
if self.__install_nokia_bins:
count += 2 # 1 for each target
if self.__install_xephyr:
count += 1
if self.__install_xephyr_icon:
count += 1
if self.__install_sb_home_shortcut:
count += 1
if self.__install_desktop_links:
count += 1
return count
def __taskInstallScratchbox(self):
'''Downloads scratchbox installer, installs scratchbox, removes the
installer'''
self.__say("Downloading %s installer" % (SB_NAME))
sb_installer_fn = download_file(SB_INSTALLER_URL)
if os.path.isfile("%s/etc/scratchbox-version" %(SB_PATH)):
self.__say("Upgrading %s" % (SB_NAME))
else:
self.__say("Installing %s" % (SB_NAME))
#If using proxy, give it to the command aswell
proxy = get_proxy('http')
if proxy:
proxystring = "http_proxy=%s " % proxy['http']
else:
proxystring = ""
opt = " -u %s " % self.__selected_username
# upgrade scratchbox - may change between tries
if os.path.isfile("%s/etc/scratchbox-version" %(SB_PATH)):
opt += "-U "
if not self.__host_info.has_apt_get:
#We need to set this if installing for tgz
opt += "-s %s " % SB_PATH
cmd = proxystring + sb_installer_fn + opt
# Maximum amount of times to retry installation if it fails (except for out of disk space)
tries = 3
while tries > 0:
# Check first we have enough space to attempt installation. Full install will take
# more than 80MB, but upgrading (or continuing failed installation) might be doable.
# User has been warned already, if he hasn't got enough space for full installation.
# Check if /scratchbox exists first, as it might be a symlink to another partition
if os.path.exists(SB_PATH):
stat = os.statvfs(SB_PATH)
else:
# Not found, check space in root, as that's where we are going to create the dir
stat = os.statvfs('/')
free_megs = stat.f_bavail * stat.f_bsize / 1024 / 1024
# Biggest package is 40MB, make sure we have at least double that free space
# before we retry installation
if free_megs < 80:
LOG("Less than 80MB free space, aborting installation")
raise Exception("Less than 80MB free space, aborting installation")
tries -= 1
returncode = exec_cmd(cmd, return_errorcode = True)
if returncode == RC_NO_ERROR:
# Installation successful, go to next step
break
elif returncode == RC_OUTOFDISKSPACE:
# Ran out of disk space, abort installation.
os.remove(sb_installer_fn)
# Don't log anything, as there probably is no space left in /tmp
raise Exception("Installer execution failed")
# On other error codes, retry installation, if we have tries left
if tries > 0:
LOG("%s installation failed trying again (%d tries left)" % (SB_NAME, tries))
else:
# Tried too many times, give up.
LOG("%s installation failed. Giving up." % (SB_NAME))
os.remove(sb_installer_fn)
raise Exception("Scratchbox installation failed")
LOG("Removing %s installer (%s)" % (SB_NAME, sb_installer_fn))
os.remove(sb_installer_fn)
#Add user to group, if not already there
if not has_user_group(self.__selected_username, SB_GROUP):
add_user_to_group(self.__selected_username, SB_GROUP)
def __taskInstallSdk(self):
'''Downloads SDK installer, installs SDK, removes the installer'''
global OPTIONS
self.__say("Downloading %s installer" % (PRODUCT_NAME_SHORT))
sdk_installer_fn = download_file(SDK_INSTALLER_URL,
self.__selected_username)
# make the installer really non-interactive
comment_line = "license\n"
lines = open(sdk_installer_fn).readlines()
for index, line in enumerate(lines):
if line == comment_line:
lines[index] = "# " + line
LOG("Successfully patched the SDK installer")
break
else:
raise Exception("SDK installer is strange, line %s not found!" %
comment_line)
open(sdk_installer_fn, "w").writelines(lines)
# do the installation thing
proxy = get_proxy('http')
if proxy:
cmd = "http_proxy=%s " % proxy['http']
else:
cmd = ""
cmd += "%s -d -m %s" % (sdk_installer_fn, self.__sdk_inst_m_opt_arg)
if self.__targets_exist:
if self.__remove_targets:
cmd += " -y"
else:
cmd += " -n %s" % self.__target_prefix
else:
cmd += " -y"
if OPTIONS.sourceslistfile:
cmd += " -a %s" % OPTIONS.sourceslistfile
self.__say("Installing %s targets" % PRODUCT_NAME_SHORT)
tries = 3
while (True):
try:
exec_cmd(cmd, username = self.__selected_username, set_sbox_gid = True)
except:
tries -= 1
if tries:
LOG("Failed to install SDK, %d tries left" % tries)
else:
LOG("Failed to install SDK")
raise Exception("Failed to install SDK")
pass
else:
break
LOG("Removing %s installer (%s)" % (PRODUCT_NAME_SHORT,
sdk_installer_fn))
os.remove(sdk_installer_fn)
def __taskInstallNokiaBins(self):
'''Installs Nokia Binaries into the targets'''
# different target prefix was chosen
if self.__targets_exist and not self.__remove_targets:
armel_target = self.__target_prefix + TARGET_POSTFIX_ARMEL
x86_target = self.__target_prefix + TARGET_POSTFIX_X86
# default target names
else:
armel_target = TARGET_ARMEL
x86_target = TARGET_X86
for target in [armel_target, x86_target]:
self.__say("Installing Nokia Binaries into target %s" % (target))
packages = "nokia-binaries "
if self.__install_apps:
packages += "nokia-apps "
# select target
LOG("Selecting target %s in scratchbox" % target)
cmd = "%s/tools/bin/sb-conf select %s" % (SB_PATH, target)
try:
exec_cmd(cmd, username = self.__selected_username, set_sbox_gid = True)
except:
LOG("Failed to select target %s" % target)
raise Exception("Failed to select target")
# add repo to sources.list
sources_list_fn = (
"%s/users/%s/targets/%s/etc/apt/sources.list" %
(SB_PATH, self.__selected_username, target))
LOG("Adding Nokia Binaries repo (%s) to sources.list of the "
"target (%s)" % (self.__nokia_bins_repo, sources_list_fn))
file_append_lines(
sources_list_fn,
["",
"# Repository for Nokia Binaries, added by %s" % (MY_NAME),
self.__nokia_bins_repo])
# install the binaries
LOG("Starting the installation of packages %s" % packages)
proxy = get_proxy('http')
if proxy:
cmd = "http_proxy=%s " % proxy['http']
else:
cmd = ""
cmd += ("%s/login fakeroot apt-get update && %s/login "
"fakeroot apt-get -o Acquire::Retries=5 install %s --force-yes -y" %
(SB_PATH, SB_PATH, packages))
LOG("Executing command: %s" % cmd)
tries = 3
while (True):
try:
exec_cmd(cmd, username = self.__selected_username, set_sbox_gid = True)
except:
tries -= 1
if tries:
LOG("Failed to install Nokia Binaries, %d tries left" % tries)
else:
LOG("Failed to install Nokia Binaries, giving up")
raise Exception("Failed to install Nokia Binaries, giving up")
pass
else:
break
def __taskUpgradeSdk(self):
'''Upgrade selected targets'''
self.__say("Upgrading SDK targets")
LOG("Starting to upgrade targets")
cmd = UPDATE_SCRIPT_FN
if os.path.exists(cmd):
try:
exec_cmd(cmd, username = self.__selected_username, set_sbox_gid = True)
except:
LOG("Failed to upgrade SDK, giving up")
raise Exception("Failed to upgrade SDK, giving up")
def __taskInstallXephyr(self):
'''Try to install Xephyr'''
self.__say("Installing xephyr")
if install_package('xserver-xephyr', askuser = False):
# Installation completed successfully, make sure we have the binary in path now
if not Which('Xephyr'):
raise Exception("Xephyr binary missing after trying to install Xephyr.")
else:
LOG("Failed to install Xephyr.")
raise Exception("Failed to install Xephyr.")
return
def __taskInstallXephyrShortcut(self):
'''Install Xephyr launcher icon & shell script'''
self.__say("Installing xephyr desktop shortcut")
LOG("Starting to install xephyr desktop shortcut")
desktopentry_fn = XEPHYR_DESKTOP_ENTRY_FILENAME
scriptpath = None
for path in BINPATHS:
if os.path.isdir(path):
LOG("Using path %s" % path)
scriptpath = path
break
if scriptpath:
LOG("Installing Xephyr launcher %s to %s" % (XEPHYR_SHORTCUT_FILENAME, scriptpath))
filename = "%s/%s" % (scriptpath, XEPHYR_SHORTCUT_FILENAME)
#Newer Xephyr has no -kb argument. Check if installed version supports it.
if xephyr_has_kb():
kb_arc = ' -kb'
else:
kb_arc = ''
script = ("#!/bin/sh\n"
"# Automatically created by %s\n"
"(Xephyr :2 -host-cursor -screen 800x480x16 -dpi 96 -ac%s; newgrp %s <<'END'\n"
"%s/login af-sb-init.sh stop\n"
"END\n"
") &\n"
"newgrp %s <<'END'\n"
"sleep 3\n"
"%s/login sb-conf select %s\n"
"%s/login af-sb-init.sh restart\n"
"END\n" % (MY_NAME, kb_arc, SB_GROUP, SB_PATH, SB_GROUP, SB_PATH, TARGET_X86, SB_PATH))
launcherfile = file(filename, 'wt')
launcherfile.write(script)
launcherfile.close()
p = subprocess.Popen(['chmod', '0755', '%s' % filename])
p.wait()
if p.returncode:
raise Exception("Failed to set file execution permission for %s" % filename)
p = subprocess.Popen(['chown', 'root:%s' % SB_GROUP, '%s' % filename])
p.wait()
if p.returncode:
raise Exception("Failed to set file owner")
desktopdir = get_user_desktop_dir(self.__selected_username)
if desktopdir:
desktopfilename = "%s/%s" % (desktopdir, desktopentry_fn)
LOG("Installing Xephyr desktop entry %s to %s" % (desktopentry_fn, desktopdir))
desktopentry = ("[Desktop Entry]\n"
"Version=5.0\n"
"Encoding=UTF-8\n"
"Name=Maemo5 SDK\n"
"Comment=shortcut to maemo desktop\n"
"Exec=%s\n"
"Terminal=false\n"
"Type=Application\n"
"StartupNotify=true\n" % filename)
desktopfile = file(desktopfilename, 'wt')
desktopfile.write(desktopentry)
desktopfile.close()
p = subprocess.Popen(['chmod', '0750', '%s' % desktopfilename])
p.wait()
if p.returncode:
raise Exception("Failed to set file permissions for %s" % desktopfilename)
p = subprocess.Popen(['chown', '%s' % (self.__selected_username), '%s' % desktopfilename])
p.wait()
if p.returncode:
raise Exception("Failed to set file owner for %s" % desktopfilename)
def __taskInstallSbHomeShortcut(self):
'''Install symbolic link to Scratchbox home directory to users Desktop dir'''
self.__say("Adding scratchbox home shortcut")
LOG("Starting to add scratchbox home shortcut")
shortcut_fn = "sbhome"
desktopdir = get_user_desktop_dir(self.__selected_username)
if desktopdir:
filename = "%s/%s" % (desktopdir, shortcut_fn)
if not os.path.islink(filename):
sbhomedir = "%s/users/%s/home/%s/" % (SB_PATH, self.__selected_username, self.__selected_username)
if os.path.isdir(sbhomedir):
LOG("Adding scratchbox home shortcut %s to %s" % (shortcut_fn, desktopdir))
p = subprocess.Popen(['ln', '-s', sbhomedir, filename])
p.wait()
if p.returncode:
raise Exception("Failed to create symbolic link")
else:
LOG("Couldn't find directory %s, not adding link" % (sbhomedir))
def __taskInstallLinks(self):
'''Installs html file with maemo-related links to Desktop'''
self.__say("Adding links to desktop")
htmldata = (''
''
'
'
' '
' '
' '
' '
' '
' '
''
''
'
'
'
'
'Maemo 5 '
'
'
'
'
'
'
'Getting Started
'
'Introduction
'
'Architecture
'
'Desktop Widget UI Guidelines
'
'Developer Guide
'
'Developer Tools
'
'API References
'
'Sample Example Code
'
'
'
'
'
'More Information
'
'Q & A SDK and Scratchbox
'
'Q & A Porting to Fremantle
'
'Accelerometers
'
'OpenGL-ES
'
'
'
'
'
'Community Application Repositories
'
'Extras-devel
'
'Extras-testing
'
'Uploading packages
'
''
'\n')
desktopdir = get_user_desktop_dir(self.__selected_username)
if desktopdir:
LOG("Adding links to %s" % desktopdir)
filename = '%s/%s' % (desktopdir, MAEMO_LINKS_FILENAME)
htmlfile = file(filename, 'wt')
htmlfile.write(htmldata)
htmlfile.close()
p = subprocess.Popen(['chmod', '0750', '%s' % filename])
p.wait()
if p.returncode:
raise Exception("Failed to set file permissions for %s" % filename)
p = subprocess.Popen(['chown', '%s' % (self.__selected_username), '%s' % filename])
p.wait()
if p.returncode:
raise Exception("Failed to set file owner for %s" % filename)
def __taskUninstallSb(self):
'''Uninstall scratchbox from system. If user home directory is not removed, the symbolic link is kept aswell.'''
self.__say("Uninstalling %s targets" % (SB_NAME))
LOG("Starting to uninstall %s targets" % (SB_NAME))
desktopdir = get_user_desktop_dir(self.__selected_username)
#1)Remove targets
userlist = get_scratchbox_users()
for user in userlist:
if has_user_active_sessions(user):
LOG("User %s has active scratchbox session(s), which prevent "
"%s uninstallation." % (SB_NAME, SB_NAME))
raise Exception ("User %s has active scratchbox session(s)" % user)
else:
#User doesn't have active sessions, remove targets
targets = get_user_targets(user)
if targets:
LOG("Removing targets for user %s" % (user))
for target in targets:
#LOG("Removing target %s" % (target))
remove_scratchbox_target(user, target)
#2)Remove scratchbox
self.__say("Uninstalling %s" % (SB_NAME))
LOG("Targets removed, starting to uninstall %s" % (SB_NAME))
uninstall_scratchbox(self.__host_info.has_64bit)
#3)Remove scratchbox home directory symlink, if /scratchbox dir was removed
if not os.path.isdir(SB_PATH):
shortcut_fn = 'sbhome'
filename = "%s/%s" % (desktopdir, shortcut_fn)
if os.path.islink(filename):
LOG("Removing desktop shortcut to %s, as directory no longer exists" % SB_PATH)
exec_cmd('rm %s' % filename)
#4)Remove xephyr launcher.
self.__say("%s uninstalled, removing Xephyr launcher" % (SB_NAME))
LOG("%s uninstalled, removing Xephyr launcher" % (SB_NAME))
scriptpath = None
for path in BINPATHS:
if os.path.isdir(path):
scriptpath = path
break
if scriptpath:
filename = scriptpath + "/" + XEPHYR_SHORTCUT_FILENAME
if os.path.isfile(filename):
LOG("Removing Xephyr launcher")
exec_cmd('rm %s' % filename)
if desktopdir:
#5)Remove xephyr launcher desktop icon.
filename = "%s/%s" % (desktopdir, 'xephyr.desktop')
if os.path.isfile(filename):
LOG("Removing Xephyr launcher icon")
exec_cmd('rm %s' % filename)
#6)Remove Maemo links.
filename = '%s/%s' % (desktopdir, MAEMO_LINKS_FILENAME)
if os.path.isfile(filename):
LOG("Removing Maemo links")
exec_cmd('rm "%s"' % filename)
def __getTasks(self):
'''Returns list of installation tasks to be executed in order'''
tasks = []
#Needed by sb-conf used in uninstall aswell
if self.__uninstall:
tasks.append(self.__taskUninstallSb)
else:
if self.__install_scratchbox:
tasks.append(self.__taskInstallScratchbox)
if self.__install_sdk:
tasks.append(self.__taskInstallSdk)
if self.__upgrade_sdk:
tasks.append(self.__taskUpgradeSdk)
if self.__install_nokia_bins:
tasks.append(self.__taskInstallNokiaBins)
if self.__install_xephyr:
tasks.append(self.__taskInstallXephyr)
if self.__install_xephyr_icon:
tasks.append(self.__taskInstallXephyrShortcut)
if self.__install_sb_home_shortcut:
tasks.append(self.__taskInstallSbHomeShortcut)
if self.__install_desktop_links:
tasks.append(self.__taskInstallLinks)
LOG("Got tasks: %s" % tasks)
return tasks
def run(self):
'''Runs the installation'''
tasks = self.__getTasks()
try:
for task in tasks:
if self.__abort:
LOG("Executor aborting")
self.__exit_status = self.__class__.ExitStatusAborted
break
else:
LOG("Executor starting task %s" % (task))
if task == tasks[-1]:
self.__is_running_last_task = True
LOG("Started task is the last one")
task()
# all tasks successfully completed
else:
self.__exit_status = self.__class__.ExitStatusOK
# for the last op
self.emit(self.sig_op_ended, self.__ops_done_num)
except: # CmdExecError, e:
LOG.log_exc()
self.__exit_status = self.__class__.ExitStatusError
LOG("Executor set exit status to (%s)" % (self.__exit_status))
def __say(self, msg):
'''Communicates with the main thread about installation progress using
signals'''
self.emit(self.sig_op_ended, self.__ops_done_num) # previous op ended
self.__ops_done_num += 1 # new op started
assert self.__ops_done_num <= self.__ops_total_num, "Too many ops!"
msg = "%s/%s %s" % (self.__ops_done_num, self.__ops_total_num, msg)
LOG("S: %s" % msg)
self.emit(self.sig_op_started, msg, self.__ops_done_num)
class ProgressPage(QtGui.QWizardPage):
'''Progress page'''
def __init__(self, tail_process):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
# whether the slider in the text widget has been scrolled to its
# vertical scroll bars end, it will be scrolled only once at
# initialization; when the slider is scrolled to the vertical end, the
# newly appended text will cause automatic scrolling, so the new text
# will always be visible
self.scrolled_to_vend = False
self.setSubTitle(" ")
self.status_label = QtGui.QLabel("Installing...")
self.status_label.setWordWrap(True)
self.progress_bar = QtGui.QProgressBar()
self.error_label = QtGui.QLabel("")
txt = ('Log file %s' %
(LOG.fn_log, LOG.fn_log))
self.logs_url_label = QtGui.QLabel(txt)
self.logs_url_label.setWordWrap(True)
self.logs_url_label.setOpenExternalLinks(True)
self.logs_url_label.setTextInteractionFlags(
QtCore.Qt.TextBrowserInteraction)
# some systems have such bug that file can't be opened by clicking on
# URL, so no hint on using the link, see
# https://bugs.launchpad.net/ubuntu/+source/xdg-utils/+bug/362121
self.logs_text_edit = QtGui.QTextEdit()
txt = "Logs from %s. Use Ctrl+Wheel to zoom the text." % LOG.fn_log
self.logs_text_edit.setToolTip(txt)
self.logs_text_edit.setWhatsThis(txt)
# set proper coloring of the text edit
# must be done before writing any text, otherwise black text
# will not be visible once you change background to black
#
# new in Qt 4.4: not used now, since some systems won't have it
# self.logs_text_edit.setTextBackgroundColor(QtCore.Qt.black)
palette = QtGui.QPalette()
palette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Base,
QtCore.Qt.black);
palette.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Base,
QtCore.Qt.black);
self.logs_text_edit.setPalette(palette);
self.logs_text_edit.setTextColor(QtCore.Qt.green)
# there must be some text in the editor, otherwise the color of text
# written with cursor will be black, why? go figure...
self.logs_text_edit.setPlainText("logs from %s\n" % LOG.fn_log)
self.logs_text_edit.setReadOnly(True)
# to look like a proper terminal use fixed width fonts
self.logs_text_edit.setFont(QtGui.QFont("Monospace", 10))
# cursor is used to append text to the end of the edit widget, there is
# append method but it adds extra newline, and InsertPlainText method
# inserts text at current cursor position, which can be changed by the
# user just by clicking somewhere in the edit widget
self.cursor = self.logs_text_edit.textCursor()
self.cursor.movePosition(QtGui.QTextCursor.End)
self.logs_button = QtGui.QPushButton("&Logs")
txt = "Toggles visibility of the logs view"
self.logs_button.setToolTip(txt)
self.logs_button.setWhatsThis(txt)
self.logs_button.setCheckable(True)
self.connect(self.logs_button, QtCore.SIGNAL("toggled(bool)"),
self.logsButtonToggled)
# takes the space of text edit when it is hidden
self.spacer = QtGui.QSpacerItem(0, 0,
QtGui.QSizePolicy.Minimum,
QtGui.QSizePolicy.Expanding)
self.connect(tail_process,
QtCore.SIGNAL("readyReadStandardOutput()"),
self.tailProcessReadStdout)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.status_label)
layout.addWidget(self.progress_bar)
layout.addWidget(self.error_label)
# spacer will be removed in button's toggled signal handler
layout.addItem(self.spacer)
layout.addWidget(self.logs_url_label)
layout.addWidget(self.logs_text_edit)
self.setLayout(layout)
self.logs_button.setChecked(True) # will emit toggled signal
def initializePage(self):
'''Overrides the method of QWizardPage, to initialize the page with
some default settings.'''
QtGui.QApplication.setOverrideCursor(QtCore.Qt.BusyCursor)
if self.field(FNUninstall).toBool():
self.setTitle('Uninstalling')
self.executor = ExecutorThread(
self.wizard().hostInfo,
True,
False,
False, False, False,
'',
str(self.field(FNSelectedUsername).toString()),
False,
False,
'',
False,
'',
False,
False,
False,
False)
else:
self.setTitle('Installing')
target_x86_exist = self.field(FNTargetX86Exist).toBool()
target_armel_exist = self.field(FNTargetArmelExist).toBool()
targets_exist = target_x86_exist or target_armel_exist
if self.field(FNEasyInstall).toBool():
#Easy install selected, use defaults
remove_targets = True
install_sb = True
install_sdk = True
upgrade_sdk = False
install_nokia_bins = True
install_apps = True
if Which('Xephyr'):
install_xephyr = False
else:
install_xephyr = True
install_xephyr_shortcut = True
create_sbhome_shortcut = True
install_desktop_links = True
# If default username cannot be figured out, use one selected on User selection page
selected_username = str(self.field(FNSelectedUsername).toString())
install_option = INSTALL_OPTIONS[DEFAULT_INSTALL_OPTION][1]
else:
remove_targets = self.field(FNRemoveTargets).toBool()
install_sb = self.field(FNInstallSB).toBool()
install_sdk = self.field(FNInstallSDK).toBool()
upgrade_sdk = self.field(FNUpgradeSDK).toBool()
install_option = str(self.field(FNSDKInstMOptArg).toString())
#if minimal rootstrap, don't install nokia bins
if install_sdk and install_option != INSTALL_OPTIONS[0][1]:
install_nokia_bins = self.field(FNInstallNokiaBins).toBool()
else:
install_nokia_bins = False
#nokia apps depend on nokia bins
if install_nokia_bins:
install_apps = self.field(FNInstallNokiaApps).toBool()
else:
install_apps = False
install_xephyr = self.field(FNInstallXephyr).toBool()
install_xephyr_shortcut = self.field(FNInstallXephyrShortcut).toBool()
create_sbhome_shortcut = self.field(FNCreateSbHomeShortcut).toBool()
install_desktop_links = self.field(FNInstallDesktopLinks).toBool()
selected_username = str(self.field(FNSelectedUsername).toString())
if remove_targets:
target_prefix = ""
else:
target_prefix = str(self.field(FNTargetPrefix).toString())
if install_nokia_bins:
nokia_bins_repo = str(self.field(FNNokiaBinsRepo).toString())
else:
nokia_bins_repo = ""
if remove_targets:
target_prefix = ""
else:
target_prefix = str(self.field(FNTargetPrefix).toString())
self.executor = ExecutorThread(
self.wizard().hostInfo,
False,
install_sb,
install_sdk, upgrade_sdk, install_apps,
install_option,
selected_username,
targets_exist,
targets_exist and remove_targets,
target_prefix,
install_nokia_bins,
nokia_bins_repo,
install_xephyr,
install_xephyr_shortcut,
create_sbhome_shortcut,
install_desktop_links)
self.progress_bar.setRange(0, self.executor.opsTotalNum)
self.progress_bar.setValue(0) # kinda redundant
self.connect(self.executor, self.executor.sig_op_started,
self.executorOpStarted)
self.connect(self.executor, self.executor.sig_op_ended,
self.executorOpEnded)
self.connect(self.executor, QtCore.SIGNAL("finished()"),
self.executorFinished)
self.executor.start()
# have a custom button to show/hide logs
self.wizard().setOption(QtGui.QWizard.HaveCustomButton1, True)
self.wizard().setButton(QtGui.QWizard.CustomButton1, self.logs_button)
def validatePage(self):
'''Overrides the method of QWizardPage, to remove the custom
button.'''
self.wizard().setOption(QtGui.QWizard.HaveCustomButton1, False)
# QWizard deletes the old button when setButton is used
self.wizard().setButton(QtGui.QWizard.CustomButton1, None)
return True
def isBusy(self):
'''Returns True if installation/removal is in progress'''
return self.executor.isRunning()
def executorOpStarted(self, msg, op_num):
'''Called when the thread has started executing an operation'''
self.status_label.setText(msg)
def executorOpEnded(self, op_num):
'''Called when the thread has ended executing an operation'''
self.progress_bar.setValue(op_num)
def executorFinished(self):
'''Called when executor thread has finished its jobs. This means
installation or removal is complete at this point. So this routine
reflects that to the UI'''
if self.field(FNUninstall).toBool():
self.setTitle('Uninstallation Process Completed')
else:
self.setTitle('Installation Process Completed')
# error
if self.executor.exitStatus == ExecutorThread.ExitStatusError:
if self.field(FNUninstall).toBool():
self.error_label.setText("Uninstallation was aborted by fatal error.")
else:
self.error_label.setText("Installation was aborted by fatal error.")
# show the failed op the label
self.status_label.setText(
"Failed: %s" %
self.status_label.text())
# abort
elif self.executor.exitStatus == ExecutorThread.ExitStatusAborted:
if self.field(FNUninstall).toBool():
self.error_label.setText("Uninstallation was aborted by the user.")
else:
self.error_label.setText("Installation was aborted by the user.")
self.status_label.setText("Aborted")
# ok
elif self.executor.exitStatus == ExecutorThread.ExitStatusOK:
if self.field(FNUninstall).toBool():
self.error_label.setText("Uninstallation completed successfully.")
else:
self.error_label.setText("Installation completed successfully.")
self.status_label.setText("Done")
else:
assert False, "Illegal thread exit status (%s)!" % \
(self.executor.exitStatus)
# enable the Next/Finish button
self.emit(QtCore.SIGNAL("completeChanged()"))
# Finish button will replace Next button if installation failed
# setFinalPage works with Qt versions 4.5 and higher, see the bug:
# http://www.qtsoftware.com/developer/task-tracker/index_html?method=entry&id=222140
if self.isLastPage():
if QtCore.QT_VERSION >= 0x040500:
self.setFinalPage(True)
else:
finish_btn = self.wizard().button(QtGui.QWizard.FinishButton)
finish_btn.setVisible(True)
finish_btn.setEnabled(True)
finish_btn.setDefault(True)
next_btn = self.wizard().button(QtGui.QWizard.NextButton)
next_btn.setVisible(False)
# from this page on there is nothing to cancel, so disabled
self.wizard().button(QtGui.QWizard.CancelButton).setEnabled(False)
QtGui.QApplication.restoreOverrideCursor()
def isLastPage(self):
'''Returns True if this page is the last one to show (in which case
Finish button will replace the Next button). This page will be the last
if installation fails.'''
return self.executor.exitStatus in \
[ExecutorThread.ExitStatusAborted, ExecutorThread.ExitStatusError]
def nextId(self):
'''Overrides the method of QWizardPage, not to show last page in case
installation fails.'''
# installation failed
if self.isLastPage():
return -1
# installation succeeded
else:
return QtGui.QWizardPage.nextId(self)
def isComplete(self):
'''Overrides the method of QWizardPage, to disable next button when
installation/removal is in progress.'''
if self.isBusy():
return False
else:
return True
def showAbortMsgBox(self):
'''Shows abort message box. Returns tuple of buttons texts.'''
msg_box = QtGui.QMessageBox(
QtGui.QMessageBox.Question,
"Really abort?",
"Installation processes are still running!")
# have not chosen to abort already in the past and not running last task
have_abort_later_btn = not self.executor.isAborting() and \
not self.executor.isRunningLastTask()
abort_now_btn_txt = "Abort now"
abort_later_btn_txt = "Abort later"
abort_now_info_text = (
"If you choose to '%s' all of the installation processes will be "
"killed. NOTE! This could potentially make your system "
"unstable. So use it at your own risk." % abort_now_btn_txt)
if have_abort_later_btn:
abort_later_info_text = (
"You can abort safely by choosing '%s', in which case "
"currently running installation process will be allowed to "
"complete its execution." % (abort_later_btn_txt))
# don't have the abort later button
else:
if self.executor.isRunningLastTask():
abort_later_info_text = (
"It appears that the last installation process is running. "
"Hence, installation should be over any minute now! It is "
"highly recommended to wait for the completion of "
"installation instead of aborting.")
else:
abort_later_info_text = (
"You have previously selected '%s', so installation will "
"be aborted after currently running installation process "
"completes its execution." % (abort_later_btn_txt))
info_txt = (abort_later_info_text + "
" + abort_now_info_text)
msg_box.setInformativeText(info_txt)
cancel_btn = msg_box.addButton(QtGui.QMessageBox.Cancel)
msg_box.addButton(abort_now_btn_txt, QtGui.QMessageBox.DestructiveRole)
if have_abort_later_btn:
msg_box.addButton(abort_later_btn_txt, QtGui.QMessageBox.AcceptRole)
msg_box.setDefaultButton(cancel_btn)
msg_box.exec_()
clicked_btn = msg_box.clickedButton()
LOG("Answer to abort dialog: %s" % clicked_btn.text())
# msg_box & clicked_btn will not exist after this function returns
return (clicked_btn.text(), abort_now_btn_txt, abort_later_btn_txt,
cancel_btn.text())
def onCancel(self):
'''Called if user tries to close or cancel the wizard, shows the abort
dialog and takes respective actions.'''
(clicked_btn_txt, abort_now_btn_txt,
abort_later_btn_txt, cancel_btn_txt) = self.showAbortMsgBox()
# cannot abort if installation has already ended
if clicked_btn_txt != cancel_btn_txt:
if not self.isBusy():
QtGui.QMessageBox.information(
self,
"Sorry!",
"Cannot '%s' since the installation has ended!" % \
(clicked_btn_txt),
QtGui.QMessageBox.Ok)
return
# abort later
if clicked_btn_txt == abort_later_btn_txt:
if self.executor.isRunningLastTask():
QtGui.QMessageBox.information(
self,
"Sorry!",
"Cannot '%s' since the last installation process is "\
"running!" % abort_later_btn_txt,
QtGui.QMessageBox.Ok)
else:
self.executor.abort()
# abort now: kill self & children (running installers) too
elif clicked_btn_txt == abort_now_btn_txt:
LOG("Committing suicide...")
os.killpg(os.getpid(), signal.SIGTERM)
def showEvent(self, event):
'''Overriden method of QWidget. Scrolls the slider of the vertical
scroll bar of the text edit to the end. This is done to enable
automatic scrolling of the scrollbar upon addition of the new text.
Done only once, when the page is shown for the first time.'''
QtGui.QWizardPage.showEvent(self, event)
if not self.scrolled_to_vend:
self.logsTextEditScrollToVend()
self.scrolled_to_vend = True
def logsButtonToggled(self, checked):
'''Slot for the toggled signal of the logs button. Hides/shows logs
widgets.'''
self.logs_text_edit.setVisible(checked)
self.logs_url_label.setVisible(checked)
if checked:
# when text edit is visible spacer is removed, otherwise spacer
# would visibly consume space if page is resized
self.layout().removeItem(self.spacer)
else:
# when text edit is hidden the spacer is used to take its space to
# prevent other widget from moving in the layout
self.layout().insertItem(2, self.spacer)
def logsTextEditScrollToVend(self):
'''Scrolls vertical scroll bar of the text edit widget to the end'''
vsb = self.logs_text_edit.verticalScrollBar()
vsb.setValue(vsb.maximum())
def logsTextEditRemoveLastLine(self):
'''Removes last line from the text edit'''
cursor = self.logs_text_edit.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.select(QtGui.QTextCursor.BlockUnderCursor)
cursor.deletePreviousChar()
def splitStringWithMultiCR(self, txt):
'''Splits a string containing multiple lines of text into list a of
single-line strings. Character \r is assumed to mark the beginning of a
line, whereas character \n is assumed to mark the end of a line. This
routine is used to emulate terminal carriage return (\r) handling.
returns a list of strings
txt = a string containing multiple lines of text (including \n, \r etc)
'''
txt_list = []
i_begin = 0 # index, where the next line should begin from
for i in xrange(0, len(txt)):
# \r is assumed to mark the beginning of a new line
if txt[i] == '\r':
# there could be \r just after line ending with \n or the whole
# text could begin with \r: we don't want empty string in those
# cases
if i - i_begin > 0:
txt_list.append(txt[i_begin:i])
i_begin = i
# \n is assumed to mark the end of a current line, next char after
# newline will be the start of the next line
elif txt[i] == '\n':
txt_list.append(txt[i_begin:i + 1])
i_begin = i + 1
# if text does not end with \n, get the last line
if i_begin < len(txt):
txt_list.append(txt[i_begin:len(txt)])
return txt_list
def logsTextEditAppend(self, txt):
'''Appends text into the text edit widget. If there are carriage return
characters in the text does some processing in order to emulate
terminal behavior.
txt = a string containing multiple lines of text (including \n, \r etc)
'''
vsb = self.logs_text_edit.verticalScrollBar()
at_bottom = vsb.value() == vsb.maximum()
# \r\n is just newline (apt uses it while selecting/unpacking)
txt = txt.replace("\r\n", "\n")
# number of carriage return characters in the text
cr_count = txt.count('\r')
# text without carriage return is just inserted
if cr_count == 0:
self.cursor.insertText(txt)
# text has only one carriage return in the beginning, previous line
# must be removed before the text is inserted
elif cr_count == 1 and txt.startswith('\r'):
self.logsTextEditRemoveLastLine()
self.cursor.insertText(txt)
# text has multiple carriage return characters
else:
txt_list = self.splitStringWithMultiCR(txt)
for line in txt_list:
if line.startswith('\r'):
self.logsTextEditRemoveLastLine()
self.cursor.insertText(line)
# automatically scroll, if slider was at the end of scrollbar
if at_bottom:
self.logsTextEditScrollToVend()
def tailProcessReadStdout(self):
'''A slot that is called when the tail process has some text on its
stdout. Appends that text to the text edit.'''
txt = str(self.wizard().tail_process.readAllStandardOutput())
self.logsTextEditAppend(txt)
class ConclusionPage(QtGui.QWizardPage):
'''ConclusionPage page'''
def __init__(self):
'''Constructor'''
QtGui.QWizardPage.__init__(self)
def initializePage(self):
'''Overrides the method of QWizardPage, to initialize the page with
some default settings.'''
self.setSubTitle(" ")
if self.field(FNUninstall).toBool():
self.setTitle('Uninstallation of %s Completed' % PRODUCT_NAME)
layout = QtGui.QHBoxLayout()
label = QtGui.QLabel("Scratchbox successfully uninstalled from system.\n\n"
"If you did any changes to your scratchbox home directory, "
"it is still available in system root, along with desktop "
"shortcut (if you created one when installing). You can later "
"re-install scratchbox over the old directory")
label.setWordWrap(True)
layout.addWidget(label)
else:
self.setTitle('Installation of %s Completed' % PRODUCT_NAME)
layout = QtGui.QGridLayout()
info_text_edit = QtGui.QTextEdit()
info_text_edit.setReadOnly(True)
instruction_title = QtGui.QLabel("Starting SDK UI")
layout.addWidget(instruction_title, 0, 0)
info_text_edit.setPlainText("1. Start the Xephyr xserver and Maemo desktop by clicking icon on the Desktop.\n\n"
"2. Log out and log back in OR execute the following command to "
"run scratchbox in the current terminal session.\n"
"$ newgrp %s\n\n"
"3. Login to scratchbox.\n"
"$ %s/login\n\n"
"Welcome to Scratchbox, the cross-compilation toolkit!\n"
"Use 'sb-menu' to change your compilation target.\n"
"See /scratchbox/doc/ for documentation.\n" % (SB_GROUP, SB_PATH))
info_text_edit.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred);
layout.addWidget(info_text_edit, 1, 0)
link_title = QtGui.QLabel("Useful links")
layout.addWidget(link_title, 0, 1)
link_str = ('Get Started
'
'Maemo 5 Developer Guide
'
'Hildon 2.2 UI Style
'
'Hildon 2.2 Widget UI Specification
'
'Redesigning from Maemo 4 to Maemo 5
'
'Example code
'
'API References
')
self.link_label = QtGui.QLabel()
self.link_label.setText(link_str)
self.link_label.setAlignment(QtCore.Qt.AlignTop)
self.connect(self.link_label, QtCore.SIGNAL("linkActivated(QString)"), self.LoadUrl)
layout.addWidget(self.link_label, 1, 1)
self.setLayout(layout)
def LoadUrl(self, url):
'''Loads specified URL in browser. Tries to figure out prefered one.'''
#If we just let Qt open browser, it will run as root, and we don't want that.
browsers = ['x-www-browser', 'firefox', 'iceweasel', 'opera', 'konqueror']
exe = Which('xdg-open')
if exe:
command = exe + ' ' + str(url)
user = str(self.field(FNSelectedUsername).toString())
if user:
exec_cmd(command, username = user, donotwait = True)
else:
exec_cmd(command, donotwait = True)
return
#xdg isn't installed as default on some distros, so we'll check if user has one
#of listed browsers, and use that.
for browser in browsers:
exe = Which(browser)
if exe:
command = exe + ' ' + str(url)
user = str(self.field(FNSelectedUsername).toString())
if user:
exec_cmd(command, username = user, donotwait = True)
else:
exec_cmd(command, donotwait = True)
return
PageIdProxy = 0
PageIdIntro = 1
PageIdLevel = 2
PageIdLicense = 3
PageIdUsers = 4
PageIdInstallOpts = 5
PageIdTargets = 6
PageIdPkg = 7
PageIdNokiaBins = 8
PageIdSummary = 9
PageIdProgress = 10
PageIdConclusion = 11
class InstallWizard(QtGui.QWizard):
'''Installation wizard'''
def __init__(self):
'''Constructor'''
# create own group so can kill (Abort Now) self & children during
# installation: this is redundant in shells but needed if script is
# launched from desktop e.g. by using kdesudo
global LOG
os.setpgrp()
QtGui.QWizard.__init__(self)
self.setWindowTitle(MY_NAME)
self.__host_info = HostInfo()
set_proxy(None)
# process that tails the log file
self.tail_process = QtCore.QProcess(self)
self.tail_process.start("tail -f " + LOG.fn_log)
self.setOption(QtGui.QWizard.DisabledBackButtonOnLastPage)
self.imageHandler = ImageHandler(IMAGES)
#Check system proxy settings
for param in os.environ.keys():
for key in ['http_proxy', 'HTTP_PROXY']:
if param == key:
set_proxy(os.environ[param])
break
if not self.imageHandler.downloadImage(random.randint(0, len(IMAGES)-1)):
self.setPage(PageIdProxy, ProxyPage())
self.setPage(PageIdIntro, IntroPage())
self.setPage(PageIdLevel, LevelPage(self.__host_info.has_scratchbox, self.__host_info.has_apt_get))
self.setPage(PageIdLicense, LicensePage(self.__host_info.has_64bit))
self.setPage(PageIdUsers, UsersPage(self.__host_info.has_64bit))
self.setPage(PageIdInstallOpts, InstallOptsPage(self.__host_info.has_scratchbox,
self.__host_info.scratchbox_op_name,
self.__host_info.has_xephyr,
self.__host_info.has_apt_get or
self.__host_info.has_yum or
self.__host_info.has_zypper))
self.setPage(PageIdTargets, TargetsPage())
self.setPage(PageIdPkg, PkgPage())
if HAVE_WEBKIT:
self.setPage(PageIdNokiaBins, NokiaBinsPage())
else:
self.setPage(PageIdNokiaBins, NokiaBinsPageNoWebkit())
self.setPage(PageIdSummary, SummaryPage())
self.setPage(PageIdProgress, ProgressPage(self.tail_process))
self.setPage(PageIdConclusion, ConclusionPage())
hostInfo = property(lambda self: self.__host_info)
def __del__(self):
'''destructor'''
self.tail_process.kill()
def reject(self):
'''Overridden method of QDialog to disable wizard closing when
installation/removal is in progress. Handles closing wizard by:
- pressing Esc button
- clicking Cancel button
- clicking X button on the title bar of the window
- any shortcut that can be used to close a window
- probably any other means used to close a window'''
if self.currentId() == PageIdProgress and self.currentPage().isBusy():
self.currentPage().onCancel()
return
# default behavior in other cases
QtGui.QWizard.reject(self)
def disabled_nextId(self):
'''Returns ID of page to show when the user clicks the Next button.
After the Users page if SDK not to be installed, returns the summary
page. Targets page is not shown if targets don't exist for the selected
user.
'''
if QtGui.QWizard.nextId(self) == PageIdTargets:
if self.field(FNEasyInstall).toBool():
return QtGui.QWizard.nextId(self)
else:
install_sdk = self.field(FNInstallSDK).toBool()
if not install_sdk:
return PageIdSummary
else: # sdk will be installed
target_x86_exist = self.field(FNTargetX86Exist).toBool()
target_armel_exist = self.field(FNTargetArmelExist).toBool()
if not target_x86_exist and not target_armel_exist:
return PageIdTargets + 1 # next page
if QtGui.QWizard.nextId(self) == PageIdNokiaBins:
if self.field(FNSDKInstMOptArg).toString() == INSTALL_OPTIONS[0][1]:
return PageIdNokiaBins + 1
return QtGui.QWizard.nextId(self)
def run_checks():
'''Performs some system checks, raises exception if some check fails'''
# this script must run with root privileges: installation of SDK only could
# be done without root priveleges, but the SDK installer requires VDSO to
# be set to scratchbox compatible value, which can only be done as root
if os.geteuid() != 0:
raise Exception("Root access is needed! Please run this application "
"with root privileges!")
# only 64 and 32 bit X86 systems are supported
sup_machines = ["i386", "i686", "x86_64"]
machine = os.uname()[4]
if machine not in sup_machines:
raise Exception("Operating system's machine or word size '%s' is not "
"supported. Only 32- and 64bit X86 systems are supported %s." %
(machine, sup_machines))
def main():
'''Main.'''
app = QtGui.QApplication([])
parse_options()
try:
run_checks()
except Exception, e:
QtGui.QMessageBox.critical(None, MY_NAME, str(e), QtGui.QMessageBox.Ok)
sys.exit(1)
random.seed()
global LOG
LOG = Logger() # must have root acces to re-write previous log file
socket.setdefaulttimeout(30)
wizard = InstallWizard()
wizard.setWizardStyle(QtGui.QWizard.ModernStyle)
wizard.resize(WINDOW_WIDTH, WINDOW_HEIGHT)
wizard.show()
#Older PyQt versions don't seem to set window size before it's opened
wizard.resize(WINDOW_WIDTH, WINDOW_HEIGHT)
return app.exec_()
if __name__ == "__main__":
main()