~usb-creator-hackers/usb-creator/trunk

« back to all changes in this revision

Viewing changes to usbcreator/backend.py

  • Committer: Evan Dandrea
  • Date: 2008-09-02 19:03:27 UTC
  • Revision ID: evan.dandrea@canonical.com-20080902190327-6bru0o17r3ht0w4i
Imported project.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008  Canonical, Ltd.
 
2
#
 
3
# This program is free software: you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation, either version 3 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
 
 
16
import os
 
17
import subprocess, sys
 
18
import stat
 
19
import shutil
 
20
import gobject
 
21
import dbus
 
22
from dbus.mainloop.glib import DBusGMainLoop
 
23
 
 
24
def popen(cmd):
 
25
    process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
 
26
        stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
 
27
    res = process.communicate()
 
28
    return process, res
 
29
 
 
30
def free_space(dev):
 
31
    stat = os.statvfs(dev)
 
32
    return stat.f_bsize * stat.f_bavail
 
33
 
 
34
class Backend:
 
35
    def __init__(self, frontend):
 
36
        self.source_devices = {}
 
37
        self.devices = {}
 
38
        self.frontend = frontend
 
39
        DBusGMainLoop(set_as_default=True)
 
40
        self.bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM)
 
41
        hal_obj = self.bus.get_object('org.freedesktop.Hal',
 
42
            '/org/freedesktop/Hal/Manager')
 
43
        self.hal = dbus.Interface(hal_obj, 'org.freedesktop.Hal.Manager')
 
44
 
 
45
    def set_install_source(self, source):
 
46
        # TODO: Make more consistent with install_target
 
47
        self.install_source = source
 
48
 
 
49
    def set_install_target(self, target):
 
50
        self.install_target = self.devices[target]
 
51
    
 
52
    def device_added(self, udi):
 
53
        dev_obj = self.bus.get_object("org.freedesktop.Hal", udi)
 
54
        dev = dbus.Interface(dev_obj, "org.freedesktop.Hal.Device")
 
55
        success = False
 
56
        # Look for the volume first as it may not have appeared yet when you
 
57
        # check the parent.
 
58
        if dev.PropertyExists('block.is_volume') and dev.GetProperty('block.is_volume'):
 
59
            if (dev.PropertyExists('storage.bus') and
 
60
                dev.GetProperty('storage.bus') == 'usb') and \
 
61
                dev.GetProperty('storage.removable'):
 
62
                success = True
 
63
            else:
 
64
                p = dev.GetProperty('info.parent')
 
65
                dev_obj = self.bus.get_object('org.freedesktop.Hal', p)
 
66
                d = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
 
67
                if (d.PropertyExists('storage.bus') and # necessary?
 
68
                    d.GetProperty('storage.bus') == 'usb') and \
 
69
                    d.GetProperty('storage.removable'):
 
70
                    success = True
 
71
            if success:
 
72
                self.add_device(dev)
 
73
                dev.connect_to_signal(signal_name="PropertyModified", handler_function=self.property_modified)
 
74
 
 
75
    def property_modified(self, num_changes, changes):
 
76
        # FIXME: useless until we can pass in the udi of the device that's
 
77
        # being changed.
 
78
        print 'property modified:'
 
79
        for c in changes:
 
80
            print 'change: %s' % str(c[0])
 
81
 
 
82
    def device_removed(self, udi):
 
83
        d = None
 
84
        for device in self.devices.itervalues():
 
85
            if device['udi'] == udi:
 
86
                print 'removing %s' % udi
 
87
                d = device['device']
 
88
        if d:
 
89
            self.devices.pop(d)
 
90
            # TODO: Move into the frontend in a generic function.
 
91
            to_delete = None
 
92
            m = self.frontend.dest_combo.get_model()
 
93
            iterator = m.get_iter_first()
 
94
            while iterator is not None:
 
95
                if m.get_value(iterator, 0) == d:
 
96
                    to_delete = iterator
 
97
                iterator = m.iter_next(iterator)
 
98
            if to_delete is not None:
 
99
                m.remove(to_delete)
 
100
    def add_device(self, dev):
 
101
        mountpoint = str(dev.GetProperty('volume.mount_point'))
 
102
        device = str(dev.GetProperty('block.device'))
 
103
        self.devices[device] = {
 
104
            'label' : str(dev.GetProperty('volume.label')).replace(' ', '_'),
 
105
            'fstype' : str(dev.GetProperty('volume.fstype')),
 
106
            'uuid' : str(dev.GetProperty('volume.uuid')),
 
107
            'mountpoint' : mountpoint,
 
108
            'udi' : str(dev.GetProperty('info.udi')),
 
109
            'free' : mountpoint and free_space(mountpoint) or 0,
 
110
            'device' : device
 
111
        }
 
112
        print 'new device:\n%s' % self.devices[device]
 
113
        self.frontend.add_dest(device)
 
114
    
 
115
    def detect_devices(self):
 
116
        # TODO: Handle devices with empty partition tables.
 
117
        devices = self.hal.FindDeviceByCapability('storage')
 
118
        for device in devices:
 
119
            dev_obj = self.bus.get_object('org.freedesktop.Hal', device)
 
120
            d = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
 
121
            if d.GetProperty('storage.bus') == 'usb' and \
 
122
                d.GetProperty('storage.removable'):
 
123
                if d.GetProperty('block.is_volume'):    
 
124
                    self.add_device(d)
 
125
                else:
 
126
                    children = self.hal.FindDeviceStringMatch('info.parent', device)
 
127
                    for child in children:
 
128
                        dev_obj = self.bus.get_object('org.freedesktop.Hal', child)
 
129
                        child = dbus.Interface(dev_obj, 'org.freedesktop.Hal.Device')
 
130
                        if child.GetProperty('block.is_volume'):
 
131
                            self.add_device(child)
 
132
                            # TODO: should we break here?
 
133
 
 
134
        self.bus.add_signal_receiver(self.device_added,
 
135
            "DeviceAdded",
 
136
            "org.freedesktop.Hal.Manager",
 
137
            "org.freedesktop.Hal",
 
138
            "/org/freedesktop/Hal/Manager")
 
139
        self.bus.add_signal_receiver(self.device_removed,
 
140
            "DeviceRemoved",
 
141
            "org.freedesktop.Hal.Manager",
 
142
            "org.freedesktop.Hal",
 
143
            "/org/freedesktop/Hal/Manager")
 
144
 
 
145
    def mount_iso(self, filename):
 
146
        # TODO: This should be automatically picked up by a modified
 
147
        # device_added function that handles source devices in the form of
 
148
        # CD-ROMs and mounted ISO files.  It should probably call out to a
 
149
        # separate function for this to ease readability.
 
150
        # Actually, HAL doesn't seem to support loop mounted filesystems and
 
151
        # indeed when manually mounted, the device does not appear in d-feet
 
152
        # (searching the mountpoint).  We'll probably have to just manually
 
153
        # construct the addition to self.devices, which probably makes a fair
 
154
        # amount of sense anyway.
 
155
        import tempfile
 
156
        print 'mounting %s' % filename
 
157
        tmpdir = tempfile.mkdtemp()
 
158
        res = popen('mount -o loop,ro %s %s' % (filename, tmpdir))
 
159
        if res[0].returncode:
 
160
            print 'unable to mount %s to %s' % (filename, tmpdir)
 
161
            print res[1]
 
162
        fp = None
 
163
        try:
 
164
            fp = open('%s/.disk/info' % tmpdir)
 
165
            line = fp.readline()
 
166
            line = ' '.join(line.split(' ')[:2])
 
167
            size = os.stat(filename).st_size
 
168
            self.source_devices[filename] = { 'title' : line,
 
169
                                              'filename' : filename,
 
170
                                              'size' : size }
 
171
            self.frontend.add_source(filename)
 
172
        except Exception, e:
 
173
            print 'Unable to find %s/.disk/info, not using %s' % \
 
174
                (tmpdir, filename)
 
175
            print str(e)
 
176
            fp.close()
 
177
            popen('umount %s' % tmpdir)
 
178
            popen('rmdir %s' % tmpdir)
 
179
 
 
180
        fp.close()
 
181
        popen('umount %s' % tmpdir)
 
182
        popen('rmdir %s' % tmpdir)
 
183
 
 
184
    def install_bootloader(self):
 
185
        # TODO: Needs to be moved into the generic install routine.
 
186
        # TODO: mark install_target['device'] as bootable using sfdisk or similar.
 
187
        print 'installing the bootloader to %s.' % self.install_target['device']
 
188
        process = popen('syslinux -s %s' % self.install_target['device'])[0]
 
189
        if process.returncode:
 
190
            print 'Error installing the bootloader (syslinux -s %s)' % self.install_target['device']
 
191
 
 
192
    def copy_files(self):
 
193
        # TODO: md5 on copy as well?
 
194
        tmpdir = ''
 
195
        def _copy_files():
 
196
            # TODO: debate pulling the total_size bit in from ubiquity for more
 
197
            # accurate progress.
 
198
            # TODO: Fedora's program shells out to cp and uses another thread that
 
199
            # checks the free space on the drive and updates the UI accordingly.
 
200
            # Is this worth replicating?
 
201
            #cmd = '/home/evan/usb-creator/install.py -s %s -t %s' % (tmpdir, self.install_target['mountpoint'])
 
202
            cmd = 'cp -ra %s/. %s' % (tmpdir, self.install_target['mountpoint'])
 
203
            cmd = cmd.split(' ')
 
204
            print cmd
 
205
            self.pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
 
206
            #gobject.io_add_watch(self.pipe.stdout,
 
207
            #         gobject.IO_IN | gobject.IO_HUP,
 
208
            #         self.data_available)
 
209
            #gobject.io_add_watch(self.pipe.stderr,
 
210
            #         gobject.IO_IN | gobject.IO_HUP,
 
211
            #         self.data_available)
 
212
            # Wait for the process to complete
 
213
            gobject.child_watch_add(self.pipe.pid, self.on_end)
 
214
 
 
215
        import tempfile
 
216
        print 'mounting'
 
217
        if self.install_source.lower().endswith('.iso'):
 
218
            tmpdir = tempfile.mkdtemp()
 
219
            popen('mount -o loop,ro %s %s' % (self.install_source, tmpdir))
 
220
            try:
 
221
                print 'copying from ISO...'
 
222
                _copy_files()
 
223
            finally:
 
224
                popen('umount %s' % tmpdir)
 
225
                popen('rmdir %s' % tmpdir)
 
226
        else:
 
227
            tmpdir = tempfile.mkdtemp()
 
228
            popen('mount -o ro %s %s' % (self.install_source, tmpdir))
 
229
            try:
 
230
                print 'copying from CD...'
 
231
                _copy_files()
 
232
            finally:
 
233
                popen('umount %s' % tmpdir)
 
234
    def on_end(self, pid, error_code):
 
235
        # FIXME: move into install.py
 
236
        popen('rm -rf %s/syslinux' % self.install_target['mountpoint'])
 
237
        popen('mv %s/isolinux %s/syslinux' %
 
238
            (self.install_target['mountpoint'], self.install_target['mountpoint']))
 
239
        popen('mv %s/syslinux/isolinux.cfg %s/syslinux/syslinux.cfg' %
 
240
            (self.install_target['mountpoint'], self.install_target['mountpoint']))
 
241
        print 'error_code %d' % error_code
 
242
        print 'All done.'
 
243
        self.frontend.quit()
 
244
    def data_available(self, source, condition):
 
245
        #print 'data_available'
 
246
        #return False
 
247
        data = ''
 
248
        while True:
 
249
            try:
 
250
                #c=self.pipe.read(1)
 
251
                c = source.read(1)
 
252
            except ValueError:
 
253
                break
 
254
            data+=c
 
255
            #print 'Read',data
 
256
            if c=='\n':
 
257
                break
 
258
        if data:
 
259
            print 'data: %s' % data
 
260
            self.frontend.progress(int(data.strip('\n')))
 
261
            return True
 
262
        else:
 
263
            return False
 
264
        #text = source.readline()
 
265
        #if len(text) > 0:
 
266
        #    #print 'data: %s' % text.strip('\n')
 
267
        #    self.frontend.progress(int(text.strip('\n')))
 
268
        #    return True
 
269
        #else:
 
270
        #    return False
 
271
 
 
272
    def create_persistence_file(self):
 
273
        # FIXME: Needs to get the count size from the frontend.  Also needs to tell
 
274
        # the frontend the max size for the count.  The minimum should be static,
 
275
        # something like 128 MB.
 
276
        # FIXME: move into install.py
 
277
        popen('dd if=/dev/zero of=%s/casper-rw bs=1M count=128' % self.install_target['mountpoint'])
 
278
        popen('mkfs.ext3 -F %s/casper-rw' % self.install_target['mountpoint'])
 
279
        # add persistence option to syslinux.cfg