~ubuntu-installer/wubi/trunk

1 by Agostino Russo
* Initial revision
1
# Copyright (c) 2008 Agostino Russo
2
#
3
# Written by Agostino Russo <agostino.russo@gmail.com>
4
#
5
# This file is part of Wubi the Win32 Ubuntu Installer.
6
#
7
# Wubi is free software; you can redistribute it and/or modify
8
# it under 5the terms of the GNU Lesser General Public License as
9
# published by the Free Software Foundation; either version 2.1 of
10
# the License, or (at your option) any later version.
11
#
12
# Wubi is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
# GNU Lesser General Public License for more details.
16
#
17
# You should have received a copy of the GNU Lesser General Public License
18
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
#
20
21
import os
22
import sys
3 by Agostino Russo
* Fixed loop on quit
23
import tempfile
1 by Agostino Russo
* Initial revision
24
from optparse import OptionParser
25
#TBD import modules as required at runtime
30 by Agostino Russo
* Bumped version to 9.04
26
from wubi.backends.win32 import WindowsBackend
27
from wubi.frontends.win32 import WindowsFrontend
239 by Evan Dandrea
Handle filesystem corruption when trying to uninstall Wubi
28
from wubi import errors
3 by Agostino Russo
* Fixed loop on quit
29
import logging
30
log = logging.getLogger("")
1 by Agostino Russo
* Initial revision
31
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
32
1 by Agostino Russo
* Initial revision
33
class Wubi(object):
3 by Agostino Russo
* Fixed loop on quit
34
137 by Agostino Russo
* Upped references to 9.10/karmic
35
    def __init__(self, application_name, version, revision, root_dir):
37 by Agostino Russo
* Completed cd menu page
36
        self.frontend = None
30 by Agostino Russo
* Bumped version to 9.04
37
        self.info = Info()
137 by Agostino Russo
* Upped references to 9.10/karmic
38
        self.info.root_dir = root_dir
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
39
        self.info.force_exit = False
56 by Agostino Russo
* Strip cpuid binary to reduce size
40
        self.info.version = version
41
        self.info.revision = revision
42
        self.info.application_name = application_name
61 by Agostino Russo
* Added more registry information, to be displayed in the Control
43
        self.info.version_revision = "%s-rev%s" % (self.info.version, self.info.revision)
3 by Agostino Russo
* Fixed loop on quit
44
        self.info.full_application_name = "%s-%s-rev%s" % (self.info.application_name, self.info.version, self.info.revision)
45
        self.info.full_version = "%s %s rev%s" % (self.info.application_name, self.info.version, self.info.revision)
46
47
    def run(self):
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
48
        self.info.quitting = False
70 by Agostino Russo
* Reverted grub4dos to revision 63, since revision 64 had issues when
49
        try:
50
            self.parse_commandline_arguments()
51
            self.set_logger()
52
            log.info("=== " + self.info.full_version + " ===")
53
            log.debug("Logfile is %s" % self.info.log_file)
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
54
            log.debug("sys.argv = %s" % sys.argv)
70 by Agostino Russo
* Reverted grub4dos to revision 63, since revision 64 had issues when
55
            self.backend = self.get_backend()
209 by Evan Dandrea
Handle the Wubi binary being in use when removing it.
56
            self.backend.remove_existing_binary()
70 by Agostino Russo
* Reverted grub4dos to revision 63, since revision 64 had issues when
57
            self.backend.fetch_basic_info()
58
            self.select_task()
59
        except Exception, err:
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
60
            if self.info.quitting:
61
                log.info("Quitting application")
62
            else:
63
                log.exception(err)
64
                if self.frontend:
65
                    error_messages = "\n".join([e for e in err.args if isinstance(e, basestring)])
66
                    self.frontend.show_error_message(_("An error occurred:\n\n%(error)s\n\nFor more information, please see the log file: %(log)s") % dict(error=error_messages, log=self.info.log_file))
67
            return
3 by Agostino Russo
* Fixed loop on quit
68
30 by Agostino Russo
* Bumped version to 9.04
69
    def quit(self):
70
        '''
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
71
        Sends quit signal to frontend if possible,
72
        since quit signals are originated by the frontend anyway
30 by Agostino Russo
* Bumped version to 9.04
73
        '''
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
74
        log.debug("application.quit")
59 by Agostino Russo
* Use the old uninstaller, if available, to remove an old installation
75
        if self.frontend and callable(self.frontend.quit):
76
            self.frontend.quit()
77
        else:
78
            self.on_quit()
30 by Agostino Russo
* Bumped version to 9.04
79
8 by Agostino Russo
* Added progress page
80
    def on_quit(self):
9 by Agostino Russo
* Improved quit mechanism
81
        '''
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
82
        Receives quit notification from frontend, or self.quit()
83
        sys.exit cannot be used, because it also terminates the launching process
9 by Agostino Russo
* Improved quit mechanism
84
        '''
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
85
        log.debug("application.on_quit")
86
        self.info.quitting = True
87
        if self.info.force_exit:
88
            log.debug("Forceful exit")
9 by Agostino Russo
* Improved quit mechanism
89
        log.info("sys.exit")
8 by Agostino Russo
* Added progress page
90
        sys.exit(0)
91
111 by Agostino Russo
The path of the original executable is now quoted, but the quotes
92
3 by Agostino Russo
* Fixed loop on quit
93
    def get_backend(self):
94
        '''
95
        Gets the appropriate backend for the system
96
        The backend contains system-specific libraries for tasks such as
97
        Information fetching, installation, and disinstallation
98
        '''
99
        #TBD do proper detection of backend
100
        return WindowsBackend(self)
101
102
    def get_frontend(self):
103
        '''
104
        Gets the appropriate frontend for the system
105
        '''
106
        #TBD do proper detection of frontend
37 by Agostino Russo
* Completed cd menu page
107
        if self.frontend:
108
            return self.frontend
3 by Agostino Russo
* Fixed loop on quit
109
        if self.info.use_frontend:
110
            raise NotImplemented
111
        else:
112
            Frontend = WindowsFrontend
113
        return Frontend(self)
114
115
    def select_task(self):
116
        '''
117
        Selects the appropriate task to perform and runs it
118
        '''
119
        if self.info.run_task == "install":
120
            self.run_installer()
37 by Agostino Russo
* Completed cd menu page
121
        elif self.info.run_task == "cd_boot":
122
            self.run_cd_boot()
3 by Agostino Russo
* Fixed loop on quit
123
        elif self.info.run_task == "uninstall":
124
            self.run_uninstaller()
37 by Agostino Russo
* Completed cd menu page
125
        elif self.info.run_task == "show_info":
126
            self.show_info()
180 by Agostino Russo
Fixed CD menu reboot (LP: #543032)
127
        elif self.info.run_task == "reboot":
128
            self.reboot()
34 by Agostino Russo
Refined tasklist
129
        elif self.info.cd_path or self.info.run_task == "cd_menu":
3 by Agostino Russo
* Fixed loop on quit
130
            self.run_cd_menu()
131
        else:
132
            self.run_installer()
30 by Agostino Russo
* Bumped version to 9.04
133
        self.quit()
3 by Agostino Russo
* Fixed loop on quit
134
135
    def run_installer(self):
136
        '''
137
        Runs the installer
138
        '''
34 by Agostino Russo
Refined tasklist
139
        #TBD add non_interactive mode
37 by Agostino Russo
* Completed cd menu page
140
        #TBD add cd_boot mode
80 by Agostino Russo
Completed gettext support
141
        if self.info.previous_target_dir \
142
        and os.path.isdir(self.info.previous_target_dir):
38 by Agostino Russo
Fixed ISO backup during uninstallation
143
            log.info("Already installed, running the uninstaller...")
46 by Agostino Russo
* Have a different uninstallation message when the installer is run
144
            self.info.uninstall_before_install = True
3 by Agostino Russo
* Fixed loop on quit
145
            self.run_uninstaller()
30 by Agostino Russo
* Bumped version to 9.04
146
            self.backend.fetch_basic_info()
80 by Agostino Russo
Completed gettext support
147
            if self.info.previous_target_dir \
148
            and os.path.isdir(self.info.previous_target_dir):
78 by Agostino Russo
Preliminary gettext support (to be completed)
149
                message = _("A previous installation was detected in %s.\nPlease uninstall that before continuing.")
59 by Agostino Russo
* Use the old uninstaller, if available, to remove an old installation
150
                message = message % self.info.previous_target_dir
151
                log.error(message)
87 by Agostino Russo
* Ask for confirmation before quitting
152
                self.get_frontend().show_error_message(message)
30 by Agostino Russo
* Bumped version to 9.04
153
                self.quit()
279.1.1 by openbcbc at gmail
Prevent installation on EFI systems (LP: #694242)
154
        if self.info.efi:
155
            message = "Wubi does not currently support EFI"
156
            log.error(message)
157
            self.get_frontend().show_error_message(message)
158
            self.quit()
3 by Agostino Russo
* Fixed loop on quit
159
        log.info("Running the installer...")
59 by Agostino Russo
* Use the old uninstaller, if available, to remove an old installation
160
        self.frontend = self.get_frontend()
37 by Agostino Russo
* Completed cd menu page
161
        self.frontend.show_installation_settings()
162
        log.info("Received settings")
9 by Agostino Russo
* Improved quit mechanism
163
        self.frontend.run_tasks(self.backend.get_installation_tasklist())
29 by Agostino Russo
* Activated download manager
164
        log.info("Almost finished installing")
219.1.3 by Jean-Baptiste Lallement
* Implement --noninteractive mode
165
        if not self.info.non_interactive:
166
            self.frontend.show_installation_finish_page()
8 by Agostino Russo
* Added progress page
167
        log.info("Finished installation")
37 by Agostino Russo
* Completed cd menu page
168
        if self.info.run_task == "reboot":
169
            self.reboot()
3 by Agostino Russo
* Fixed loop on quit
170
171
    def run_uninstaller(self):
172
        '''
30 by Agostino Russo
* Bumped version to 9.04
173
        Runs the uninstaller interface
3 by Agostino Russo
* Fixed loop on quit
174
        '''
175
        log.info("Running the uninstaller...")
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
176
        if not self.info.previous_target_dir \
177
        or not os.path.isdir(self.info.previous_target_dir):
178
            log.error("No previous target dir found, exiting")
179
            return
180
        if self.backend.run_previous_uninstaller():
181
            return
182
        self.frontend = self.get_frontend()
235 by Evan Dandrea
Unbreak finding a local ISO and remove the backup directory code. find_iso would depend on it, but we don't create that directory so early on and we're not backing up ISOs, so there's no sense in keeping it around.
183
        self.frontend.show_uninstallation_settings()
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
184
        log.info("Received settings")
239 by Evan Dandrea
Handle filesystem corruption when trying to uninstall Wubi
185
        try:
186
            self.frontend.run_tasks(self.backend.get_uninstallation_tasklist())
187
        except errors.WubiCorruptionError:
188
            # TODO we should present a reboot button along with this.
189
            err = _("Files on your computer are corrupted. A disk check has "
190
                    "been scheduled and will be performed at the next boot. "
191
                    "Please reboot your computer now.")
192
            self.frontend.show_error_message(err)
193
            self.quit()
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
194
        log.info("Almost finished uninstalling")
219.1.3 by Jean-Baptiste Lallement
* Implement --noninteractive mode
195
        if not self.info.uninstall_before_install and not self.info.non_interactive:
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
196
            self.frontend.show_uninstallation_finish_page()
29 by Agostino Russo
* Activated download manager
197
        log.info("Finished uninstallation")
3 by Agostino Russo
* Fixed loop on quit
198
199
    def run_cd_menu(self):
200
        '''
201
        If Wubi is run off a CD, run the CD menu (old umenu)
202
        '''
203
        log.info("Running the CD menu...")
204
        self.frontend = self.get_frontend()
38 by Agostino Russo
Fixed ISO backup during uninstallation
205
        if not self.info.cd_distro:
87 by Agostino Russo
* Ask for confirmation before quitting
206
            self.frontend.show_error_message(_("No CD detected, cannot run CD menu"))
38 by Agostino Russo
Fixed ISO backup during uninstallation
207
            self.quit()
35 by Agostino Russo
* Added a stubs for username and timezone
208
        self.frontend.show_cd_menu_page()
209
        log.info("CD menu finished")
37 by Agostino Russo
* Completed cd menu page
210
        self.select_task()
211
212
    def run_cd_boot(self):
69 by Agostino Russo
Added CD-boot-helper functionality
213
        if not self.info.cd_distro:
78 by Agostino Russo
Preliminary gettext support (to be completed)
214
            message = _("Could not find any valid CD.\nCD boot helper can only be used with a Live CD.")
69 by Agostino Russo
Added CD-boot-helper functionality
215
            log.error(message)
87 by Agostino Russo
* Ask for confirmation before quitting
216
            self.get_frontend().show_error_message(message)
69 by Agostino Russo
Added CD-boot-helper functionality
217
            self.quit()
218
        if self.info.previous_target_dir:
219
            log.info("Already installed, running the uninstaller...")
220
            self.info.uninstall_before_install = True
221
            self.run_uninstaller()
222
            self.backend.fetch_basic_info()
223
            if self.info.previous_target_dir:
78 by Agostino Russo
Preliminary gettext support (to be completed)
224
                message = _("A previous installation was detected in %s.\nPlease uninstall that before continuing.")
69 by Agostino Russo
Added CD-boot-helper functionality
225
                message = message % self.info.previous_target_dir
226
                log.error(message)
87 by Agostino Russo
* Ask for confirmation before quitting
227
                self.get_frontend().show_error_message(message)
69 by Agostino Russo
Added CD-boot-helper functionality
228
                self.quit()
37 by Agostino Russo
* Completed cd menu page
229
        log.info("Running the CD boot helper...")
69 by Agostino Russo
Added CD-boot-helper functionality
230
        self.frontend = self.get_frontend()
231
        self.frontend.show_cdboot_page()
232
        log.info("CD boot helper confirmed")
233
        self.frontend.run_tasks(self.backend.get_cdboot_tasklist())
234
        log.info("Almost finished installing")
235
        self.frontend.show_installation_finish_page()
236
        log.info("Finished installation")
237
        if self.info.run_task == "reboot":
238
            self.reboot()
37 by Agostino Russo
* Completed cd menu page
239
240
    def reboot(self):
241
        log.info("Rebooting")
242
        tasklist = self.backend.get_reboot_tasklist()
243
        tasklist.run()
244
245
    def show_info(self):
246
        self.backend.show_info()
3 by Agostino Russo
* Fixed loop on quit
247
248
    def parse_commandline_arguments(self):
249
        '''
250
        Parses commandline arguments
251
        '''
252
        usage = "%s [options]" % self.info.application_name
253
        parser = OptionParser(usage=usage, version=self.info.full_version)
37 by Agostino Russo
* Completed cd menu page
254
        parser.add_option("--quiet", action="store_const", const="quiet", dest="verbosity", help="run in quiet mode, only critical error messages are displayed")
255
        parser.add_option("--verbose", action="store_const", const="verbose", dest="verbosity", help="run in verbose mode, all messages are displayed")
256
        parser.add_option("--install", action="store_const", const="install", dest="run_task", help="run the uninstaller, it will first look for an existing uninstaller, otherwise it will run itself in uninstaller mode")
257
        parser.add_option("--uninstall", action="store_const", const="uninstall", dest="run_task", help="run the installer, if an existing installation is detected it will be uninstalled first")
258
        parser.add_option("--cdmenu", action="store_const", const="cd_menu", dest="run_task", help="run the CD menu selector")
259
        parser.add_option("--cdboot", action="store_const", const="cd_boot", dest="run_task", help="install a CD boot helper program")
69 by Agostino Russo
Added CD-boot-helper functionality
260
        parser.add_option("--showinfo", action="store_const", const="show_info", dest="run_task", help="open the distribution website for more information")
34 by Agostino Russo
Refined tasklist
261
        parser.add_option("--nobittorrent", action="store_true", dest="no_bittorrent", help="Do not use the bittorrent downloader")
57 by Agostino Russo
* Use a local 32 bit CD/ISO even if the CPU arch is 64 bit
262
        parser.add_option("--32bit", action="store_true", dest="force_i386", help="Force installation of 32 bit version")
34 by Agostino Russo
Refined tasklist
263
        parser.add_option("--skipmd5check", action="store_true", dest="skip_md5_check", help="Skip md5 checks")
264
        parser.add_option("--skipsizecheck", action="store_true", dest="skip_size_check", help="Skip disk size checks")
265
        parser.add_option("--skipmemorycheck", action="store_true", dest="skip_memory_check", help="Skip memory size checks")
266
        parser.add_option("--noninteractive", action="store_true", dest="non_interactive", help="Non interactive mode")
267
        parser.add_option("--test", action="store_true", dest="test", help="Test mode")
268
        parser.add_option("--debug", action="store_true", dest="debug", help="Debug mode")
27 by Agostino Russo
* Fixed main installation page data
269
        parser.add_option("--drive", dest="target_drive", help="Target drive")
217 by Colin Watson
Declare --size as taking an int argument (LP: #799780).
270
        parser.add_option("--size", type="int", dest="installation_size_mb", help="Installation size in MB")
27 by Agostino Russo
* Fixed main installation page data
271
        parser.add_option("--locale", dest="locale", help="Linux locale")
84 by Agostino Russo
Reverted previous changes as suggested by Evan Dandrea and instead
272
        parser.add_option("--force-wubi", action="store_true", dest="force_wubi", help="Show Wubi option in CD menu even when using a DVD")
27 by Agostino Russo
* Fixed main installation page data
273
        parser.add_option("--language", dest="language", help="Language")
274
        parser.add_option("--username", dest="username", help="Username")
275
        parser.add_option("--password", dest="password", help="Password (md5)")
30 by Agostino Russo
* Bumped version to 9.04
276
        parser.add_option("--distro", dest="distro_name", help="Distro")
277
        parser.add_option("--accessibility", dest="accessibility", help="Accessibility")
27 by Agostino Russo
* Fixed main installation page data
278
        parser.add_option("--webproxy", dest="web_proxy", help="Web proxy")
279
        parser.add_option("--isopath", dest="iso_path", help="Use specified ISO")
240 by Evan Dandrea
* Add support for disk image installation (LP: #859696). Thanks
280
        parser.add_option("--dimagepath", dest="dimage_path", help="Use specified disk image")
30 by Agostino Russo
* Bumped version to 9.04
281
        parser.add_option("--exefile", dest="original_exe", default=None, help="Used to indicate the original location of the executable in case of self-extracting files")
3 by Agostino Russo
* Fixed loop on quit
282
        parser.add_option("--log-file", dest="log_file", default=None, help="use the specified log file, if omitted a log is created in your temp directory, if the value is set to 'none' no log is created")
283
        parser.add_option("--interface", dest="use_frontend", default=None, help="use the specified user interface, ['win32']")
284
        (options, self.args) = parser.parse_args()
285
        self.info.__dict__.update(options.__dict__)
34 by Agostino Russo
Refined tasklist
286
        if self.info.test:
287
            self.info.debug = True
288
        if self.info.debug:
36 by Agostino Russo
Modified log formatting
289
            self.info.verbosity = "verbose"
111 by Agostino Russo
The path of the original executable is now quoted, but the quotes
290
        if self.info.original_exe:
291
            original_exe = self.info.original_exe.strip()
292
            if original_exe[0] in ['"',"'"] and original_exe[-1]  in ['"',"'"]:
293
                self.info.original_exe = original_exe[1:-1].strip()
294
            if os.path.basename(self.info.original_exe).startswith("uninstall-"):
295
                self.info.run_task = "uninstall"
3 by Agostino Russo
* Fixed loop on quit
296
56 by Agostino Russo
* Strip cpuid binary to reduce size
297
    def set_logger(self, log_to_console=True):
3 by Agostino Russo
* Fixed loop on quit
298
        '''
299
        Adjust the application root logger settings
300
        '''
301
        # file logging
302
        if not self.info.log_file or self.info.log_file.lower() != "none":
303
            if not self.info.log_file:
304
                fname = self.info.full_application_name + ".log"
305
                dname = tempfile.gettempdir()
306
                self.info.log_file = os.path.join(dname, fname)
307
            handler = logging.FileHandler(self.info.log_file)
308
            formatter = logging.Formatter('%(asctime)s %(levelname)-6s %(name)s: %(message)s', datefmt='%m-%d %H:%M')
309
            handler.setFormatter(formatter)
310
            handler.setLevel(logging.DEBUG)
311
            log.addHandler(handler)
312
        # console logging
56 by Agostino Russo
* Strip cpuid binary to reduce size
313
        if log_to_console and not bool(self.info.original_exe):
314
            handler = logging.StreamHandler()
315
            formatter = logging.Formatter('%(message)s', datefmt='%m-%d %H:%M')
316
            handler.setFormatter(formatter)
317
            if self.info.verbosity == "verbose":
318
                handler.setLevel(logging.DEBUG)
319
            elif self.info.verbosity == "quiet":
320
                handler.setLevel(logging.ERROR)
321
            else:
322
                handler.setLevel(logging.INFO)
323
            log.addHandler(handler)
324
        log.setLevel(logging.DEBUG)
108 by Agostino Russo
* Disabled ISO backup because download resume is not fully supported
325
30 by Agostino Russo
* Bumped version to 9.04
326
327
class Info(object):
328
329
    def __str__(self):
330
        return "Info(%s)" % str(self.__dict__)
331
332
333
if __name__ == "__main__":
334
    app = Wubi()
335
    app.run()