NAME

App::USBKeyCopyCon - GUI console for bulk copying of USB keys

SYNOPSIS

To launch the GUI application that this module implements, simply run the supplied wrapper script:

usb-key-copy-con

DESCRIPTION

This module implements an application for bulk copying USB flash drives (storage devices). The application was developed to run on Linux and is probably not particularly portable to other platforms.

From a user's perspective the operation is simple:

  1. insert a 'master' USB key when prompted - the contents of the key will be copied into a temporary directory on the hard drive, after which the key can be removed

  2. insert blank keys into all available USB ports - the app will detect when each new key is inserted, start the copy process and alert the user on completion

  3. repeat step 2 as required

The program can write to multiple keys in parallel. It can also use filtering on device parameters to only overwrite devices which match the vendor name and storage capacity specified - other devices will be ignored.

The specifics of reading the master key, preparing a blank key (formatting parameters etc) are implemented in short 'profile' scripts (a reader and a writer). You can supply your own profile scripts if your requirements differ from those provided.

DEVELOPER INFORMATION

The remainder of the documentation is targetted at developers who wish to modify or customise the application.

The application uses the Gtk2 GUI toolkit. The wrapper script instantiates a single application object like this:

use App::USBKeyCopyCon;

App::USBKeyCopyCon->new->run;

The constructor is responsible for building the user interface and the run method invokes the Gtk2 event loop. UI events are dispatched as method calls on the application object.

ATTRIBUTES

The application object has the following attributes (with correspondingly named accessor methods):

app_win

The main Gtk2::Window object.

automount_state

Stores the enabled state ('true' or 'false') of the GNOME/Nautilus media automount option. The function will be disabled on startup and this value will be restored on exit.

capacity_combo

The Gtk2::ComboBox object for the device filter 'Capacity' drop-down menu.

capacity_entry

The Gtk2::Entry object for the device filter 'Capacity' text entry box.

console

The Gtk2::TextView object used for writing output messages.

current_keys

A hash for tracking which (non-master) keys are currently inserted and what stage each copy process is at. The hash key is the device 'UDI' and the value is a hash of device dtails .

current_state

Used to control which mode the application is in:

MASTER-WAIT    waiting for the user to insert the master key
MASTER-COPYING waiting for the master key 'reader' script to complete
MASTER-COPIED  waiting for the user to remove the master key
COPYING        waiting for the user to insert blank keys
exit_status

Used by a SIGCHLD handler to track the exit status of the copy scripts. The key is a process ID and the value is the exist status returned by wait.

hal

The DBus object ('org.freedesktop.Hal.Manager') from which device add/remove events are received.

key_rack

The Gtk2::HBox object containing the widgets representing currently inserted keys.

master_info

A hash of device details for the 'master' USB key.

master_root

The path to the temp directory containing the copy of the master key.

mount_dir

The path to the temp directory containing temporary mount points.

The volume label read from the master key and to be applied to the copies.

options

A hash of option name/value pairs passed in from comman-line arguments by the wrapper script.

profiles

A hash of details of known profiles. Used to populate the profile drop-down menu on the confirm master key dialog.

selected_profile

The name of the copying profile which will be used to select reader/writer scripts.

selected_sound

Pathname of the currently selected sound file, to be played when copying is complete.

sudo_path

If the script was run by a non-root user and sudo is available, this string will be populated with the pathname of either gksudo or sudo. When running the read/writer scripts the string will be prepended onto the commands.

temp_root

The temp directory selected by the user. The application will create a subdirectory for the copy of the master key and for temporary mount points.

vendor_combo

The Gtk2::ComboBox object for the device filter 'Vendor' drop-down menu.

vendor_entry

The Gtk2::Entry object for the device filter 'Vendor' text entry box.

volume_label

The volume label which will be passed to the writer script.

PROFILES

The tasks of reading a master key and writing to a blank key are delegated to 'reader' and 'writer' scripts. A pair of reader/writer scripts is supplied but the application also supports using different scripts as dictated by a user selection. The supplied scripts assume file-by-file copying and format the blank keys with a VFAT filesystem. An alternate script might for example, use dd to write a complete filesystem image in a single operation.

A pair of scripts is referred to as a copying 'profile'. The user can select a profile via a command-line option or from a drop-down list when confirming the master key.

The supplied scripts are called:

copyfiles-reader.sh
copyfiles-writer.sh

A profile does not need to include a reader script. If a profile which only includes a writer script is selected (via the command-line options) then the application will go immediately into the mode of waiting for blank keys.

Profile Script API

The filename of the reader script must end with -reader (followed by an optional extension) and similarly, the filename of the writer script must end with -writer.

The reader/writer scripts do not have to be shell scripts - they merely need to be executable. The application ignores the file extension if it is present.

Both reader and writer scripts are assumed to have succeeded if they have an exit status of 0. A non-zero exit status will be considered a failure.

When the master key reader script is invoked, the following environment variables will be set:

USB_BLOCK_DEVICE    e.g.: /dev/sdb
USB_MOUNT_DIR       e.g.: /tmp/usb-copy.nnnnn/mount/sdb
USB_MASTER_ROOT     e.g.: /tmp/usb-copy.nnnnn/master

The writer script will be passed the same set of variables and one extra:

USB_VOLUME_NAME     e.g.: FREE-STUFF

Be warned that this variable may be empty - depending on what was returned from running dosfslabel against the master key. It is entirely reasonable for a custom writer script to ignore this variable altogether and either use a hardcoded volume label or not use one at all.

The writer script can also indicate progress (for updating the progress bar in the icon) by writing lines to STDOUT in the following format:

{x/y}

Where '{' is the first character on a line; 'x' is an integer indicating the number of steps completed; and 'y' is an integer indicating the total number of steps. For example if the script output this line:

{4/8}

the status icon would be updated to indicate 50% complete.

METHODS

Constructor

The new method is used to create an application object. It in turn calls BUILD to create and populate the application window and hook into HAL (the Hardware Abstraction Layer) via DBus to get notifications of devices been added/removed.

add_key_to_rack ( key_info )

Called from hal_device_added if the newly added device matches the current device filter settings. The key_info parameter supplied is a hashref of device properties as returned by hal_device_properties. A GUI widget representing the new USB key is added to the user interface and a data structure to track the copying process is created.

build_console ( )

Called from build_ui to create the scrolled text window for displaying progress messages.

build_filters ( )

Called from build_ui to create the toolbar of drop-down menus and text entries for the device filter settings.

build_key_rack ( )

Called from build_ui to create the container widget to house the per-key status indicators.

build_menu ( )

Called from build_ui to create the application menu and hook the menu items up to handler methods.

build_ui ( )

Called from the constructor to create the main application window and populate it with Gtk widgets.

check_for_root_user ( )

Called on startup to check that either the script is running as root or that sudo is available. In the latter case, sudo (or gksudo) will be used to invoke the read/writer scripts.

If the script is not running with root permissions; and sudo is not available; and the --no-root-check option was not specified, this method will die with an appropriate error message.

clean_temp_dir ( )

Called from the run method immediately before the application exits. This method is responsible for removing the temporary directories containing the master copy of the files and the mount points for the blank keys.

When running as a non-root user, this method needs to use sudo in order to remove the files created by the reader script when it was running as root.

commandline_options ( )

This class method returns a list of recognised options in the form expected by Getopt::Long.

confirm_master_dialog ( key_info )

This method is called each time a USB key is inserted when the application is in the MASTER-WAIT state. The key_info parameter supplied is a hashref of device properties as returned by hal_device_properties. this method displays a dialog box to allow the user to confirm that the device should be used as the master key.

If the user selects 'Cancel', no further action is taken and the application goes back to waiting for a master key to be inserted.

If the user confirms the device should be used as the master, then control is passed to the start_master_read method.

copy_finished ( exit_status )

Called when a 'writer' process exits. Checks the exit status and updates the icon in the key rack (0 = success, non-zero = failure).

disable_automount ( )

This method is called at startup to query GConf for the current GNOME/Nautilus media automount status ('true'/'false' for enabled/disabled). The current state is saved and then the value is set to false. The operation should fail silently in non-GNOME environments.

disable_filter_inputs ( )

This method is called from require_master_key to disable the menu and text entry widgets on the device filter toolbar.

enable_filter_inputs ( )

This method is called from require_master_key to enable the menu and text entry widgets on the device filter toolbar.

find_command ( command )

Takes a command name and returns the path to the first matching executable file found in a directory listed in the $PATH environment variable. Returns undef if no match found.

fork_copier ( key_info )

Called from add_key_to_rack. Forks a 'writer' process and collects its STDOUT+STDERR via a pipe.

get_volume_label ( device )

Called from confirm_master_dialog when collecting information about the key which was just inserted. Current implementation simply runs the dosfslabel command.

hal_device_added ( udi )

Called to handle a 'DeviceAdded' event from HAL via DBus. Delegates to start_master_read if the app is waiting for a master key. Otherwise checks whether the new device parameters match the current filter settings and delegates to add_key_to_rack if they do.

hal_device_properties ( udi )

Called from hal_device_added to query HAL. Returns a hash(ref) of device details. The global variable %hal_device_added defines which attributes returned from HAL will appear in the hash and which keys they will be mapped to.

hal_device_removed ( udi )

Called to handle a 'DeviceRemoved' event from HAL via DBus. Delegates to remove_key_from_rack if the application is in the COPYING state.

init_dbus_watcher ( )

Called from the constructor to hook up device-add events to the hal_device_added method and device-remove events to hal_device_removed.

master_copy_finished ( exit_status )

Called when the 'reader' process exits. Checks the exit status and updates the application state to <MASTER-COPIED> on success or MASTER-WAIT on failure.

match_device_filter ( key_info )

Called from hal_device_added and returns true if the device matches the current filter parameters, or false otherwise.

on_copier_pipe_read ( fileno, condition, udi )

Handler for data received from a 'writer' process. Updates the status icon for the device to indicate progress.

on_master_pipe_read ( fileno, condition, udi )

Handler for data received from the master key 'reader' process. Copies output from the process to the console widget.

on_menu_edit_preferences ( )

Handler for the Edit > Preferences menu item - not currently implemented.

on_menu_file_new ( )

Handler for the File > New menu item. Resets the application state via require_master_key.

on_menu_file_quit ( )

Handler for the File > Quit menu item. Exits the Gtk event loop, which returns control to the run method.

on_menu_help_about ( )

Handler for the Help > About menu item. Displays 'About' dialog.

play_sound_file ( sound_file )

This method takes a pathname to a sound file (e.g.: a .wav) and plays it. The current implementation simply runs the the SOX play command - it should probably use GStreamer

reader_script ( )

Returns the path to the script from the currently selected profile, which will be used to read the master key. Will return undef if the selected profile does not include a reader script.

ready_to_write ( )

This method is called after the master key has been read (or immediately on startup if the selected profile does not use a reader script) and puts the application into the mode of waiting for blank keys to be inserted.

remove_key_from_rack ( udi )

Called from hal_device_removed to remove the indicator widget corresponding to the USB key which has just been removed.

require_master_key ( )

Called from the constructor to put the app in the MASTER-WAIT mode (waiting for the master key to be inserted). Can also be called from the on_menu_file_new menu event handler.

restore_automount ( )

This method is called at exit time restore the original GConf setting for the GNOME/Nautilus media automount function.

run ( )

This method is called from the wrapper script. It's job is to run the Gtk event loop and when that exits, to call clean_temp_dir and then return.

say ( message )

Appends a message to the console widget. (Note, the caller is responsible for supplying the newline characters).

scan_for_profiles ( )

Populates the hash of profile data in the profiles attribute.

select_profile ( profile_name )

This method is used to select which reader/writer scripts will be used. At present there is one hard-coded call to this method in the constructor. Ideally, the user would select from all available profile scripts in the 'confirm master' dialog.

set_temp_root ( pathname )

Called from confirm_master_dialog based on the temp directory selected by the user.

start_master_read ( key_info )

Called from hal_device_added to fork off a 'reader' process to slurp in the contents of the master key.

sudo_wrap ( command env-var-names )

If the script is run by a non-root user and sudo is available and the --no-root-check option was not specified, this method will return a command string which wraps the supplied command in a call to either gksudo or sudo. For all other cases, command is returned unmodified.

The gksudo command is preferred since it gives the user a GUI prompt window if it is necessary to prompt for a password. This method handles the different semantics required to pass environment variables through gksudo and sudo.

tick ( )

This timer event handler is used to take the child process exit status values collected by the SIGCHLD handler and pass them to master_copy_finished or copy_finished as appropriate.

update_key_progress ( udi, status )

Called from on_copier_pipe_read to update the status icon for a specified USB key device. The progress parameter is a number in the range 0-10 for copies in progress; -1 for a copy that has failed (non-zero exit status from the 'writer' process); or -2 to indicate a device which did not match the filter settings and is being ignored.

writer_script ( )

Returns the path to the script from the currently selected profile, which will be used to write to the blank keys.

AUTHOR

Grant McLean, <grantm at cpan.org>

BUGS

Please report any bugs or feature requests to bug-app-usbkeycopycon at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=App-USBKeyCopyCon. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc App::USBKeyCopyCon

You can also look for information at:

COPYRIGHT & LICENSE

Copyright 2009 Grant McLean, all rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.