root/shotwell/tags/shotwell-0.4.2/src/CameraTable.vala

Revision 871, 13.5 KB (checked in by adam, 8 months ago)

Eliminated two string literal build warnings.

Line 
1/* Copyright 2009 Yorba Foundation
2 *
3 * This software is licensed under the GNU Lesser General Public License
4 * (version 2.1 or later).  See the COPYING file in this distribution.
5 */
6
7#if !NO_CAMERA
8
9public class DiscoveredCamera {
10    public GPhoto.Camera gcamera;
11    public string uri;
12   
13    public DiscoveredCamera(GPhoto.Camera gcamera, string uri) {
14        this.gcamera = gcamera;
15        this.uri = uri;
16    }
17}
18
19public class CameraTable {
20    private const int UPDATE_DELAY_MSEC = 500;
21   
22    private static CameraTable instance = null;
23    private static bool camera_update_scheduled = false;
24   
25    // these need to be ref'd the lifetime of the instance, of which there is only one
26    private Hal.Context hal_context = new Hal.Context();
27    private DBus.Connection hal_conn = null;
28
29    private GPhoto.Context null_context = new GPhoto.Context();
30    private GPhoto.CameraAbilitiesList abilities_list;
31   
32    private Gee.HashMap<string, DiscoveredCamera> camera_map = new Gee.HashMap<string, DiscoveredCamera>(
33        str_hash, str_equal, direct_equal);
34
35    public signal void camera_added(DiscoveredCamera camera);
36   
37    public signal void camera_removed(DiscoveredCamera camera);
38   
39    private CameraTable() {
40        string? errmsg = init_hal();
41        if (errmsg != null) {
42            critical("%s", errmsg);
43            AppWindow.error_message(
44               _("Shotwell could not initialize a connection to the HAL daemon (hald).  This usually means it is not running or not ready.  Rebooting may solve this problem.\n\nShotwell cannot detect cameras without the HAL daemon."));
45        }
46       
47        // because loading the camera abilities list takes a bit of time and slows down app
48        // startup, delay loading it (and notifying any observers) for a small period of time,
49        // after the dust has settled
50        Timeout.add(500, delayed_init);
51    }
52   
53    private string? init_hal() {
54        // set up HAL connection to monitor for device insertion/removal, to look for cameras
55        try {
56            hal_conn = DBus.Bus.get(DBus.BusType.SYSTEM);
57        } catch (DBus.Error err) {
58            hal_context = null;
59           
60            return "Unable to get DBus system connection (%s)".printf(err.message);
61        }
62       
63        // don't unref hal_conn from hereafter because DBus complains about it is not closed and
64        // there is no binding for Hal.Connection.close().  Note that the connection is a shared
65        // connection, and the docs also say not to close shared connections.  We'll just hold on
66        // to the connection, even if HAL initialization fails.
67       
68        if (!hal_context.set_dbus_connection(hal_conn.get_connection())) {
69            hal_context = null;
70           
71            return "Unable to set DBus connection for HAL";
72        }
73
74        DBus.RawError raw = DBus.RawError();
75        if (!hal_context.init(ref raw)) {
76            hal_context = null;
77           
78            return "Unable to initialize context (%s)".printf(raw.message);
79        }
80
81        if (!hal_context.set_device_added(on_device_added)) {
82            hal_context = null;
83           
84            return "Unable to register device-added callback";
85        }
86       
87        if (!hal_context.set_device_removed(on_device_removed)) {
88            hal_context = null;
89           
90            return "Unable to register device-removed callback";
91        }
92       
93        return null;
94    }
95   
96    private bool delayed_init() {
97        try {
98            init_camera_table();
99            update_camera_table();
100        } catch (GPhotoError err) {
101            error("%s", err.message);
102        }
103       
104        return false;
105    }
106   
107    public static CameraTable get_instance() {
108        if (instance == null)
109            instance = new CameraTable();
110       
111        return instance;
112    }
113   
114    public Gee.Iterable<DiscoveredCamera> get_cameras() {
115        return camera_map.values;
116    }
117   
118    public int get_count() {
119        return camera_map.size;
120    }
121   
122    public DiscoveredCamera? get_for_uri(string uri) {
123        return camera_map.get(uri);
124    }
125
126    private void do_op(GPhoto.Result res, string op) throws GPhotoError {
127        if (res != GPhoto.Result.OK)
128            throw new GPhotoError.LIBRARY("[%d] Unable to %s: %s", (int) res, op, res.as_string());
129    }
130   
131    private void init_camera_table() throws GPhotoError {
132        do_op(GPhoto.CameraAbilitiesList.create(out abilities_list), "create camera abilities list");
133        do_op(abilities_list.load(null_context), "load camera abilities list");
134    }
135   
136    // USB (or libusb) is a funny beast; if only one USB device is present (i.e. the camera),
137    // then a single camera is detected at port usb:.  However, if multiple USB devices are
138    // present (including non-cameras), then the first attached camera will be listed twice,
139    // first at usb:, then at usb:xxx,yyy.  If the usb: device is removed, another usb:xxx,yyy
140    // device will lose its full-path name and be referred to as usb: only.
141    //
142    // This function gleans the full port name of a particular port, even if it's the unadorned
143    // "usb:", by using HAL.
144    private string? esp_usb_to_udi(int camera_count, string port, out string full_port) {
145        // sanity
146        assert(camera_count > 0);
147       
148        if (hal_context == null) {
149            debug("ESP: HAL context not established");
150           
151            return null;
152        }
153       
154        debug("ESP: camera_count=%d port=%s", camera_count, port);
155
156        DBus.RawError raw = DBus.RawError();
157        string[] udis = hal_context.find_device_by_capability("camera", ref raw);
158       
159        string[] usbs = new string[0];
160        foreach (string udi in udis) {
161            if (hal_context.device_get_property_string(udi, "info.subsystem", ref raw) == "usb")
162                usbs += udi;
163        }
164
165        // if GPhoto detects one camera, and HAL reports one USB camera, all is swell
166        if (camera_count == 1 && usbs.length ==1) {
167            string usb = usbs[0];
168           
169            int hal_bus = hal_context.device_get_property_int(usb, "usb.bus_number", ref raw);
170            int hal_device = hal_context.device_get_property_int(usb, "usb.linux.device_number",
171                ref raw);
172
173            if (port == "usb:") {
174                // the most likely case, so make a full path
175                full_port = "usb:%03d,%03d".printf(hal_bus, hal_device);
176            } else {
177                full_port = port;
178            }
179           
180            debug("ESP: port=%s full_port=%s udi=%s", port, full_port, usb);
181           
182            return usb;
183        }
184
185        // with more than one camera, skip the mirrored "usb:" port
186        if (port == "usb:") {
187            debug("ESP: Skipping %s", port);
188           
189            return null;
190        }
191       
192        // parse out the bus and device ID
193        int bus, device;
194        if (port.scanf("usb:%d,%d", out bus, out device) < 2)
195            error("ESP: Failed to scanf %s", port);
196       
197        foreach (string usb in usbs) {
198            int hal_bus = hal_context.device_get_property_int(usb, "usb.bus_number", ref raw);
199            int hal_device = hal_context.device_get_property_int(usb, "usb.linux.device_number", ref raw);
200           
201            if ((bus == hal_bus) && (device == hal_device)) {
202                full_port = port;
203               
204                debug("ESP: port=%s full_port=%s udi=%s", port, full_port, usb);
205
206                return usb;
207            }
208        }
209       
210        debug("ESP: No UDI found for port=%s", port);
211       
212        return null;
213    }
214   
215    public static string get_port_uri(string port) {
216        return "gphoto2://[%s]/".printf(port);
217    }
218
219    private void update_camera_table() throws GPhotoError {
220        // need to do this because virtual ports come and go in the USB world (and probably others)
221        GPhoto.PortInfoList port_info_list;
222        do_op(GPhoto.PortInfoList.create(out port_info_list), "create port list");
223        do_op(port_info_list.load(), "load port list");
224
225        GPhoto.CameraList camera_list;
226        do_op(GPhoto.CameraList.create(out camera_list), "create camera list");
227        do_op(abilities_list.detect(port_info_list, camera_list, null_context), "detect cameras");
228       
229        Gee.HashMap<string, string> detected_map = new Gee.HashMap<string, string>(str_hash, str_equal,
230            str_equal);
231       
232        // go through the detected camera list and glean their ports
233        for (int ctr = 0; ctr < camera_list.count(); ctr++) {
234            string name;
235            do_op(camera_list.get_name(ctr, out name), "get detected camera name");
236
237            string port;
238            do_op(camera_list.get_value(ctr, out port), "get detected camera port");
239           
240            debug("Detected %s @ %s", name, port);
241           
242            // do some USB ESP, skipping ports that cannot be deduced
243            if (port.has_prefix("usb:")) {
244                string full_port;
245                string udi = esp_usb_to_udi(camera_list.count(), port, out full_port);
246                if (udi == null)
247                    continue;
248               
249                port = full_port;
250            }
251
252            detected_map.set(port, name);
253        }
254       
255        // find cameras that have disappeared
256        DiscoveredCamera[] missing = new DiscoveredCamera[0];
257        foreach (DiscoveredCamera camera in camera_map.values) {
258            GPhoto.PortInfo port_info;
259            do_op(camera.gcamera.get_port_info(out port_info),
260                "retrieve missing camera port information");
261           
262            GPhoto.CameraAbilities abilities;
263            do_op(camera.gcamera.get_abilities(out abilities), "retrieve camera abilities");
264           
265            if (detected_map.has_key(port_info.path)) {
266                debug("Found page for %s @ %s in detected cameras", abilities.model, port_info.path);
267               
268                continue;
269            }
270           
271            debug("%s @ %s missing", abilities.model, port_info.path);
272           
273            missing += camera;
274        }
275       
276        // have to remove from hash map outside of iterator
277        foreach (DiscoveredCamera camera in missing) {
278            GPhoto.PortInfo port_info;
279            do_op(camera.gcamera.get_port_info(out port_info),
280                "retrieve missing camera port information");
281           
282            GPhoto.CameraAbilities abilities;
283            do_op(camera.gcamera.get_abilities(out abilities), "retrieve missing camera abilities");
284
285            debug("Removing from camera table: %s @ %s", abilities.model, port_info.path);
286
287            camera_map.unset(get_port_uri(port_info.path));
288           
289            camera_removed(camera);
290        }
291
292        // add cameras which were not present before
293        foreach (string port in detected_map.keys) {
294            string name = detected_map.get(port);
295            string uri = get_port_uri(port);
296
297            if (camera_map.has_key(uri)) {
298                // already known about
299                debug("%s @ %s already registered, skipping", name, port);
300               
301                continue;
302            }
303           
304            int index = port_info_list.lookup_path(port);
305            if (index < 0)
306                do_op((GPhoto.Result) index, "lookup port %s".printf(port));
307           
308            GPhoto.PortInfo port_info;
309            do_op(port_info_list.get_info(index, out port_info), "get port info for %s".printf(port));
310           
311            // this should match, every time
312            assert(port == port_info.path);
313           
314            index = abilities_list.lookup_model(name);
315            if (index < 0)
316                do_op((GPhoto.Result) index, "lookup camera model %s".printf(name));
317
318            GPhoto.CameraAbilities camera_abilities;
319            do_op(abilities_list.get_abilities(index, out camera_abilities),
320                "lookup camera abilities for %s".printf(name));
321               
322            GPhoto.Camera gcamera;
323            do_op(GPhoto.Camera.create(out gcamera), "create camera object for %s".printf(name));
324            do_op(gcamera.set_abilities(camera_abilities), "set camera abilities for %s".printf(name));
325            do_op(gcamera.set_port_info(port_info), "set port info for %s on %s".printf(name, port));
326           
327            debug("Adding to camera table: %s @ %s", name, port);
328           
329            DiscoveredCamera camera = new DiscoveredCamera(gcamera, uri);
330            camera_map.set(uri, camera);
331           
332            camera_added(camera);
333        }
334    }
335   
336    private static void on_device_added(Hal.Context context, string udi) {
337        debug("on_device_added: %s", udi);
338       
339        schedule_camera_update();
340    }
341   
342    private static void on_device_removed(Hal.Context context, string udi) {
343        debug("on_device_removed: %s", udi);
344       
345        schedule_camera_update();
346    }
347   
348    // Device add/removes often arrive in pairs; this allows for a single
349    // update to occur when they come in all at once
350    private static void schedule_camera_update() {
351        if (camera_update_scheduled)
352            return;
353       
354        Timeout.add(UPDATE_DELAY_MSEC, background_camera_update);
355        camera_update_scheduled = true;
356    }
357   
358    private static bool background_camera_update() {
359        debug("background_camera_update");
360   
361        try {
362            get_instance().update_camera_table();
363        } catch (GPhotoError err) {
364            debug("Error updating camera table: %s", err.message);
365        }
366       
367        camera_update_scheduled = false;
368
369        return false;
370    }
371}
372
373#endif
374
Note: See TracBrowser for help on using the browser.