App-USBKeyCopyCon

 view release on metacpan or  search on metacpan

lib/App/USBKeyCopyCon.pm  view on Meta::CPAN

  usb-key-copy-con

=head1 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:

=over 4

=item 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

=item 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

=item 3

repeat step 2 as required

=back

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.

=head1 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 C<run>
method invokes the Gtk2 event loop.  UI events are dispatched as method calls
on the application object.

=cut

use Moose;

use Gtk2 -init;
use Glib qw(TRUE FALSE);
use Gtk2::SimpleMenu;

use App::USBKeyCopyCon::Chrome;

use Net::DBus;
use Net::DBus::GLib;
use Net::DBus::Dumper;

use POSIX        qw(:sys_wait_h);
use IO::Handle   qw();
use File::Path   qw(mkpath rmtree);
use File::Spec   qw();

use Data::Dumper;

has 'current_state'    => ( is => 'rw', isa => 'Str',  default => '' );
has 'sudo_path'        => ( is => 'rw', isa => 'Str',  default => '' );
has 'master_info'      => ( is => 'rw' );
has 'options'          => ( is => 'rw', default => sub { {} } );
has 'profiles'         => ( is => 'rw', default => sub { {} } );
has 'selected_profile' => ( is => 'rw', isa => 'Str',  default => '' );
has 'automount_state'  => ( is => 'rw', isa => 'Str',  default => undef );
has 'temp_root'        => ( is => 'rw', isa => 'Str',  default => undef );
has 'master_root'      => ( is => 'rw', isa => 'Str',  default => undef );
has 'mount_dir'        => ( is => 'rw', isa => 'Str',  default => undef );
has 'volume_label'     => ( is => 'rw', isa => 'Str',  default => '' );
has 'selected_sound'   => ( is => 'rw', isa => 'Str',  default => '' );
has 'current_keys'     => ( is => 'ro', default => sub { {} } );
has 'exit_status'      => ( is => 'ro', default => sub { {} } );
has 'app_win'          => ( is => 'rw', isa => 'Gtk2::Window' );
has 'key_rack'         => ( is => 'rw', isa => 'Gtk2::Container' );
has 'console'          => ( is => 'rw', isa => 'Gtk2::TextView' );
has 'vendor_combo'     => ( is => 'rw', isa => 'Gtk2::ComboBox' );
has 'vendor_entry'     => ( is => 'rw', isa => 'Gtk2::Entry' );
has 'capacity_combo'   => ( is => 'rw', isa => 'Gtk2::ComboBox' );
has 'capacity_entry'   => ( is => 'rw', isa => 'Gtk2::Entry' );
has 'hal'              => ( is => 'rw', isa => 'Net::DBus::RemoteObject' );



my @menu_entries = (
    # name,       stock id,          label
    [ "FileMenu", undef,             "_File"        ],
    [ "EditMenu", undef,             "_Edit"        ],
    [ "HelpMenu", undef,             "_Help"        ],
    # name,       stock id,          label,               accelerator,  tooltip,                  action
    [ "New",      'gtk-new',         "_New master key",   "<control>N", "Re-read the master key", 'file_new' ],
    [ "Quit",     'gtk-quit',        "_Quit",             "<control>Q", "Quit",                   'file_quit' ],
    [ "Prefs",    'gtk-preferences', "_Preferences",      "<control>E", "About",                  'edit_preferences' ],
    [ "About",    'gtk-about',       "_About",            "<control>A", "About",                  'help_about' ],
);

my $menu_ui = "<ui>
  <menubar name='MenuBar'>
    <menu action='FileMenu'>
      <menuitem action='New'/>
      <menuitem action='Quit'/>
    </menu>
    <menu action='EditMenu'>
      <menuitem action='Prefs'/>
    </menu>
    <menu action='HelpMenu'>
      <menuitem action='About'/>
    </menu>
  </menubar>
</ui>";

my %hal_key_map = (
    'info.udi'                     => 'udi',
    'info.vendor'                  => 'vendor',
    'info.product'                 => 'product',
    'block.device'                 => 'block_device',
    'storage.removable.media_size' => 'media_size',
    'linux.sysfs_path'             => 'sysfs_path',
);

my $gconf_automount_path = '/apps/nautilus/preferences/media_automount';

use constant VENDOR_EXACT      => 0;
use constant VENDOR_PATTERN    => 1;
use constant VENDOR_ANY        => 2;
use constant CAPACITY_EXACT    => 0;
use constant CAPACITY_MINIMUM  => 1;
use constant CAPACITY_ANY      => 2;


sub BUILD {
    my $self = shift;

    $self->check_for_root_user;
    $self->set_temp_root('/tmp');
    $self->scan_for_profiles;
    $self->select_profile;
    $self->disable_automount;

    my($path) = __FILE__ =~ m{^(.*)[.]pm$};
    $path = File::Spec->rel2abs($path) . "/copy-complete.wav";
    $self->selected_sound($path);

    $self->build_ui;

    $self->init_dbus_watcher;

    $self->require_master_key;
}


sub sudo_wrap {
    my($self, $command, @env_vars) = @_;

    my $sudo = $self->sudo_path or return $command;

    if($sudo =~ /gksudo/) {
        my $msg = "The application 'usb-key-copy-con' requires administrative "
                . "privileges to access USB flash drives";
        return qq{$sudo --preserve-env --message "$msg" "$command"};
    }

    my $env = join '', map { qq($_="$ENV{$_}" ) } @env_vars;
    return qq{$sudo $env $command}
}

lib/App/USBKeyCopyCon.pm  view on Meta::CPAN

    $self->say("Edit>Preferences - not implemented\n");
}


sub on_menu_help_about {
    my $self = shift;

    my $dialog = Gtk2::Dialog->new(
        'About: usb-key-copy-con',
        $self->app_win,
        [qw/modal destroy-with-parent/],
        'gtk-close' => 'ok',
    );
    $dialog->set_default_size (90, 80);

    my $panel = Gtk2::VBox->new(FALSE, 12);

    my $title = Gtk2::Label->new;
    $title->set_markup("<span font_desc='sans 20'> USB Key Copy Console </span>");
    $title->set_selectable(TRUE);
    $panel->pack_start($title, FALSE, FALSE, 10);

    my $version = Gtk2::Label->new;
    $version->set_markup("<span font_desc='sans 16'>Version: $VERSION</span>");
    $version->set_selectable(TRUE);
    $panel->pack_start($version, FALSE, FALSE, 0);

    my $author = Gtk2::Label->new;
    my $detail = '(c) 2009 Grant McLean &lt;grantm@cpan.org&gt;';
    $author->set_markup("  <span font_desc='sans 10'>$detail</span>  ");
    $author->set_selectable(TRUE);
    $panel->pack_start($author, FALSE, FALSE, 10);

    $dialog->vbox->pack_start($panel, FALSE, FALSE, 4);
    $dialog->show_all;

    $dialog->run;

    $dialog->destroy;
}


sub build_menu {
    my $self = shift;

    foreach my $item (@menu_entries) {
        if(exists $item->[5]) {
            my $action = 'on_menu_' . $item->[5];
            $item->[5] = sub { $self->$action(@_) };
        }
    }
    my $actions = Gtk2::ActionGroup->new("Actions");
    $actions->add_actions(\@menu_entries, undef);

    my $ui = Gtk2::UIManager->new;
    $ui->insert_action_group($actions, 0);
    $self->app_win->add_accel_group($ui->get_accel_group);

    $ui->add_ui_from_string ($menu_ui);

    return $ui->get_widget('/MenuBar');
}


sub build_key_rack {
    my $self = shift;

    my $box = Gtk2::HBox->new(FALSE, 4);

    $self->key_rack($box);

    return $box;
}


sub build_filters {
    my $self = shift;

    my $box = Gtk2::HBox->new(FALSE, 4);

    my $label = Gtk2::Label->new("Filter parameters:");
    $box->pack_start($label, FALSE, FALSE, 10);

    my $vendor_combo = Gtk2::ComboBox->new_text;
    $vendor_combo->append_text('Exactly match vendor');
    $vendor_combo->append_text('Pattern match vendor');
    $vendor_combo->append_text('Match any vendor');
    $vendor_combo->set_active(VENDOR_EXACT);
    #$vendor_combo->signal_connect(changed => sub { $self->apply_filter(); });
    $box->pack_start($vendor_combo, FALSE, FALSE, 10);
    $self->vendor_combo($vendor_combo);

    my $vendor_entry = Gtk2::Entry->new;
    $vendor_entry->set_width_chars(11);
    $vendor_entry->set_text('');
    $box->pack_start($vendor_entry, FALSE, FALSE, 0);
    $self->vendor_entry($vendor_entry);

    my $capacity_combo = Gtk2::ComboBox->new_text;
    $capacity_combo->append_text('Exactly match capacity');
    $capacity_combo->append_text('Match minimum capacity');
    $capacity_combo->append_text('Match any capacity');
    $capacity_combo->set_active(CAPACITY_EXACT);
    #$capacity_combo->signal_connect(changed => sub { $self->apply_filter(); });
    $box->pack_start($capacity_combo, FALSE, FALSE, 10);
    $self->capacity_combo($capacity_combo);

    my $capacity_entry = Gtk2::Entry->new;
    $capacity_entry->set_width_chars(11);
    $capacity_entry->set_text('');
    $box->pack_start($capacity_entry, FALSE, FALSE, 0);
    $self->capacity_entry($capacity_entry);

    return $box;
}


sub disable_filter_inputs { shift->_set_filter_sensitive(FALSE) }
sub enable_filter_inputs  { shift->_set_filter_sensitive(TRUE)  }




( run in 2.837 seconds using v1.01-cache-2.11-cpan-cdf2f3d4e48 )