Browse Source

TripalImporter class is working. GFF3 importer ported

Stephen Ficklin 8 years ago
parent
commit
88563d14cd

+ 123 - 0
tripal/api/tripal.importer.api.inc

@@ -0,0 +1,123 @@
+<?php
+/**
+ * Implements hook_handle_uplaoded_file().
+ *
+ * This is a Tripal hook that allows the module to set the proper
+ * parameters for a file uploaded via the Tripal HTML5 uploader.
+ *
+ * @param $filename
+ *   The name of the file uploaded
+ * @param $filepath
+ *   The path to the file
+ * @param $type
+ *   The category or type of file.
+ *
+ * @return
+ *   A Drupal managed file ID.
+ */
+function hook_handle_uploaded_file($filename, $filepath, $type) {
+
+}
+
+/**
+ * Retrieves a list of TripalImporter Importers.
+ *
+ * The TripalImporter classes can be added by a site developer that wishes
+ * to create a new data loader.  The class file should
+ * be placed in the [module]/includes/TripalImporter directory.  Tripal will
+ * support any loader as long as it is in this directory and extends the
+ * TripalImporter class.
+ *
+ * @return
+ *   A list of TripalImporter names.
+ */
+function tripal_get_importers() {
+  $importers = array();
+
+  $modules = module_list(TRUE);
+  foreach ($modules as $module) {
+    // Find all of the files in the tripal_chado/includes/fields directory.
+    $loader_path = drupal_get_path('module', $module) . '/includes/TripalImporter';
+    $loader_files = file_scan_directory($loader_path, '/.inc$/');
+    // Iterate through the fields, include the file and run the info function.
+    foreach ($loader_files as $file) {
+      $class = $file->name;
+      module_load_include('inc', $module, 'includes/TripalImporter/' . $class);
+      if (class_exists($class) and is_subclass_of($class, 'TripalImporter')) {
+        $importers[] = $class;
+      }
+    }
+  }
+  return $importers;
+}
+
+/**
+ * Loads the TripalImporter class file into scope.
+ *
+ * @param $class
+ *   The TripalImporter class to include.
+ *
+ * @return
+ *   TRUE if the field type class file was found, FALSE otherwise.
+ */
+function tripal_load_include_importer_class($class) {
+
+  $modules = module_list(TRUE);
+  foreach ($modules as $module) {
+    $file_path = realpath(".") . '/' . drupal_get_path('module', $module) . '/includes/TripalImporter/' . $class . '.inc';
+    if (file_exists($file_path)) {
+      module_load_include('inc', $module, 'includes/TripalImporter/' . $class);
+      if (class_exists($class)) {
+        return TRUE;
+      }
+    }
+  }
+  return FALSE;
+}
+
+/**
+ * Imports data into the database.
+ *
+ * Tripal provides the TripalImporter class to allow site developers to
+ * create their own data loaders.  Site users can then use any data loader
+ * implemented for the site by submitting the form that comes with the
+ * TripalImporter impelmentation.  This function runs the importer using the
+ * arguments provided by the user.
+ *
+ * @param $import_id
+ *   The ID of the import record.
+ * @throws Exception
+ */
+function tripal_run_importer($import_id, $job_id = NULL) {
+  $details = db_select('tripal_import', 'ti')
+    ->fields('ti')
+    ->condition('ti.import_id', $import_id)
+    ->execute()
+    ->fetchObject();
+
+  if ($details) {
+    $details->arguments = unserialize($details->arguments);
+    if ($details->job_id) {
+      $details->job = tripal_get_job($details->job_id);
+    }
+    if ($details->fid) {
+      $details->file = file_load($details->fid);
+    }
+
+    $class = $details->class;
+    tripal_load_include_importer_class($class);
+    if (class_exists($class)) {
+      $loader_name = $class::$name;
+      $loader = new $class();
+      try {
+        $loader->preRun($details);
+        $loader->run($details, $job_id);
+        $loader->postRun($details);
+      }
+      catch (Exception $e) {
+        throw new Exception('Cannot run loader, ' . $loader_name . ': ' .  $e->getMessage());
+      }
+    }
+  }
+}
+

+ 0 - 32
tripal/api/tripal.loaders.api.inc

@@ -1,32 +0,0 @@
-<?php
-/**
- * Retrieves a list of TripalLoader class names.
- *
- * The TripalLoader classes can be added by a site developer and should be
- * placed in the [module]/includes/TripalLoader directory.  Tripal will support
- * any loader as long as it is in this directory and extends the TripalLoader
- * class.  To support dynamic inclusion of new loaders this function
- * will look for TripalLoader class files and return them all.
- *
- * @return
- *   A list of TripalLoader class names.
- */
-function tripal_get_data_loaders() {
-  $loaders = array();
-
-  $modules = module_list(TRUE);
-  foreach ($modules as $module) {
-    // Find all of the files in the tripal_chado/includes/TripalLoader/ directory.
-    $loader_path = drupal_get_path('module', $module) . '/includes/TripalLoader';
-    $loader_files = file_scan_directory($fields_path, '/.*\.inc$/');
-    // Iterate through the loaders, include the file and run the info function.
-    foreach ($loader_files as $file) {
-      $loader = preg_replace('/\.inc$/', '', $file->name);
-      module_load_include('inc', $module, 'includes/TripalLoader/' . $loader . '.inc');
-      if (class_exists($loader) and is_subclass_of($field_type, 'TripalLoader')) {
-        $loaders[] = $loader;
-      }
-    }
-  }
-  return $loaders;
-}

+ 26 - 29
tripal/includes/TripalLoader/TripalLoader.inc → tripal/includes/TripalImporter.inc

@@ -1,5 +1,5 @@
 <?php
-class TripalLoader {
+class TripalImporter {
   // --------------------------------------------------------------------------
   //                     EDITABLE STATIC CONSTANTS
   //
@@ -14,53 +14,52 @@ class TripalLoader {
    */
   public static $name = 'Tripal Loader';
 
+  /**
+   * The machine name for this loader. This name will be used to construct
+   * the URL for the loader.
+   */
+  public static $machine_name = 'tripal_loader';
+
   /**
    * A brief description for this loader.  This description will be
    * presented to the site user.
    */
   public static $description = 'A base loader for all Tripal loaders';
 
+  /**
+   * An array containing the extensions of allowed file types.
+   */
+  public static $file_types = array();
+
 
-  // --------------------------------------------------------------------------
-  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
-  // --------------------------------------------------------------------------
   /**
-   * The loader can be executed as a Tripal job, in that case we want
-   * to keep track of the job record.
+   * Provides information to the user about the file upload.  Typically this
+   * may include a description of the file types allowed.
    */
-  protected $job;
+  public static $upload_description = '';
 
+  /**
+   * The title that should appear above the upload button.
+   */
+  public static $upload_title = 'File Upload';
 
 
   // --------------------------------------------------------------------------
   //                          CONSTRUCTORS
   // --------------------------------------------------------------------------
   /**
-   * Instantiates a new TripalLoader object.
+   * Instantiates a new TripalImporter object.
    *
    * @param $job_id
    */
-  public function __construct($job_id = NULL) {
-    $this->job = NULL;
-    if ($job_id) {
-      $this->job = tripal_get_job($job_id);
-    }
+  public function __construct() {
+
   }
 
   // --------------------------------------------------------------------------
   //                     PROTECTED FUNCTIONS
   // --------------------------------------------------------------------------
 
-  protected function setJobProgress($percentage) {
-     if (!is_numeric($percent_complete)) {
-       throw new Exception('TripalLoader: Percent complete must be numeric.');
-     }
-     if (!$this->job) {
-       throw new Exception('TripalLoader: This loader is not associated with a job. Cannot set the job progress.');
-     }
-
-     tripal_set_job_progress($this->job->job_id, $percentage);
-  }
 
   // --------------------------------------------------------------------------
   //                     OVERRIDEABLE FUNCTIONS
@@ -76,15 +75,13 @@ class TripalLoader {
   public function formValidate($form, &$form_state) {
 
   }
-  public function preRun() {
+  public function preRun($details) {
 
   }
-  public function run() {
-     if (!$this->job) {
-       throw new Exception('TripalLoader: This loader is not associated with a job. Cannot run the loader.');
-     }
+  public function run($details, $job_id = NULL) {
+
   }
-  public function postRun() {
+  public function postRun($details) {
 
   }
 

+ 163 - 0
tripal/includes/tripal.importer.inc

@@ -0,0 +1,163 @@
+<?php
+
+
+/**
+ * Build the form for a TripalImporter implementation.
+ */
+function tripal_get_importer_form($form, &$form_state, $class) {
+
+  tripal_load_include_importer_class($class);
+
+  $form['importer_class'] = array(
+    '#type' => 'value',
+    '#value' => $class,
+  );
+  $form['file'] = array(
+    '#type' => 'fieldset',
+    '#title' => t($class::$upload_title),
+    '#description' => t($class::$upload_description) . ' Either upload the file or if the file exists on the Tripal server please provide the full path.'
+  );
+  $form['file']['file_upload']= array(
+    '#type' => 'html5_file',
+    '#title' => '',
+    '#description' => 'Remember to click the "Upload" button below to send ' .
+      'your file to the server.  This interface is capable of uploading very ' .
+      'large files.  If you are disconnected you can return, reload the file and it ' .
+      'will resume where it left off.  Once the file is uploaded the "Upload '.
+      'Progress" will indicate "Complete".  If the file is already present on the server ' .
+      'then the status will quickly update to "Complete".',
+    '#usage_type' => 'tripal_importer',
+    '#usage_id' => 0,
+  );
+
+  $form['file']['file_local']= array(
+    '#type'          => 'textfield',
+    '#title'         => '',
+    '#description'   => t('If the file is local to the Tripal server please provide the full path here.'),
+  );
+
+  $importer = new $class();
+  $element = array();
+  $form['class_elements'] = $importer->form($element, $form_state);
+
+  $form['button'] = array(
+    '#type' => 'submit',
+    '#value' => t('Import ' . $class),
+    '#weight' => 10,
+  );
+  return $form;
+}
+
+/**
+ * Validate function for the tripal_get_importer_form form().
+ */
+function tripal_get_importer_form_validate($form, &$form_state) {
+  $class = $form_state['values']['importer_class'];
+
+  // Get the form values for the file.
+  $file_local = trim($form_state['values']['file_local']);
+  $file_upload = trim($form_state['values']['file_upload']);
+
+  // If the file is local make sure it exists on the local filesystem.
+  if ($file_local) {
+    // check to see if the file is located local to Drupal
+    $file_local = trim($file_local);
+    $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $file_local;
+    if (!file_exists($dfile)) {
+      // if not local to Drupal, the file must be someplace else, just use
+      // the full path provided
+      $dfile = $file_local;
+    }
+    if (!file_exists($dfile)) {
+      form_set_error('file_local', t("Cannot find the file on the system. Check that the file exists or that the web server has permissions to read the file."));
+    }
+  }
+
+  // The user must provide at least an uploaded file or a local file path.
+  if (!$file_upload and !$file_local) {
+    form_set_error('file_local', t("You must provide a file either by uploading one or specifying a path on the Tripal server.  Did you click the 'Upload' button?"));
+  }
+
+  // Now allow the loader to do validation of it's form additions.
+  $importer = new $class();
+  $importer->formValidate($form, $form_state);
+}
+
+/**
+ * Submit function for the tripal_get_importer_form form().
+ */
+function tripal_get_importer_form_submit($form, &$form_state) {
+  global $user;
+
+  $arguments = $form_state['values'];
+
+  // Remove the file_local and file_upload args. We'll add in a new
+  // full file path and the fid instead.
+  unset($arguments['file_local']);
+  unset($arguments['file_upload']);
+  unset($arguments['form_build_id']);
+  unset($arguments['form_token']);
+  unset($arguments['form_id']);
+  unset($arguments['op']);
+  unset($arguments['button']);
+
+  $file_local = trim($form_state['values']['file_local']);
+  $file_upload = trim($form_state['values']['file_upload']);
+  $class = $form_state['values']['importer_class'];
+
+  // Sumbit a job for this loader.
+  $fname = '';
+  $fid = NULL;
+  if ($file_local) {
+    $fname = preg_replace("/.*\/(.*)/", "$1", $file_local);
+    $arguments['file_path'] = $file_local;
+    $arguments['fid'] = NULL;
+  }
+  if ($file_upload) {
+    $fid = $file_upload;
+    $file = file_load($fid);
+    $fname = $file->filename;
+    $arguments['file_path'] =  base_path() . drupal_realpath($file->uri);
+    $arguments['fid'] = $file_upload;
+  }
+
+  try {
+
+    $values = array(
+      'uid' => $user->uid,
+      'class' => $class,
+      'arguments' => serialize($arguments),
+      'submit_date' => time(),
+    );
+    if ($fid) {
+      $values['fid'] = $fid;
+    }
+    // Add the importer to the tripal_import table.
+    $import_id = db_insert('tripal_import')
+      ->fields($values)
+      ->execute();
+
+    // Add a job to run the importer.
+    $args = array($import_id);
+    $includes = array(
+      module_load_include('inc', 'tripal', 'api/tripal.importer.api'),
+    );
+    $job_id = tripal_add_job("Import " . $class::$upload_title . ": $fname", 'tripal',
+        'tripal_run_importer', $args, $user->uid, 10, $includes);
+
+    // Now associate the job_id with the import.
+    db_update('tripal_import')
+      ->fields(array(
+        'job_id' => $job_id,
+      ))
+      ->condition('import_id', $import_id)
+      ->execute();
+
+    // Now allow the loader to do it's own submit if needed.
+    $importer = new $class();
+    $importer->formSubmit($form, $form_state);
+  }
+  catch (Exception $e) {
+    drupal_set_message('Cannot submit import: ' . $e->getMessage(), 'error');
+  }
+}

+ 0 - 1
tripal/includes/tripal_admin_usage_page.inc

@@ -12,7 +12,6 @@ function tripal_admin_usage_page() {
   $breadcrumb[] = l('Tripal', 'admin/tripal');
   drupal_set_breadcrumb($breadcrumb);
   tripal_add_d3js();
-  //drupal_add_js(drupal_get_path ('module', 'tripal') . '/theme/js/tripal_galaxy.dashboard.js');
   drupal_add_css(drupal_get_path ('module', 'tripal') . '/theme/css/tripal.dashboard.css');
   drupal_add_library('system', 'drupal.collapse');
   $output = '<h2>Tripal Administrative Notifications and Info</h2>';

+ 20 - 0
tripal/theme/js/tripal.file.js

@@ -0,0 +1,20 @@
+(function($) {
+  Drupal.behaviors.TripalFile = {
+    attach: function (context, settings) {
+
+      var tripal_files = new TripalUploader();
+      
+      $(".tripal-html5-file-upload-table-key").each(function(index) {
+        var form_key = $(this).val()
+        tripal_files.addUploadTable(form_key, {
+          'table_id' : '#tripal-html5-file-upload-table-' + form_key,
+          'submit_id': '#tripal-html5-file-upload-submit-' + form_key,
+          'category' : [form_key],
+          'cardinality' : 1,
+          'target_id' : 'tripal-html5-upload-fid-' + form_key,
+          'module' : 'tripal',
+        });
+      });
+    }
+  }
+}) (jQuery);

+ 96 - 48
tripal/tripal.install

@@ -195,6 +195,9 @@ function tripal_schema() {
   $schema['tripal_admin_notfications'] = tripal_tripal_admin_notifications_schema();
   return $schema;
 }
+/**
+ * Returns the Drupal Schema API array for the tripal_jobs table.
+ */
 function tripal_tripal_jobs_schema() {
   return array(
     'fields' => array(
@@ -300,6 +303,78 @@ function tripal_tripal_jobs_schema() {
     'primary key' => array('job_id'),
   );
 }
+/**
+ * Returns the Drupal Schema API array for the tripal_jobs table.
+ */
+function tripal_tripal_import_schema() {
+  return array(
+    'fields' => array(
+      'import_id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => 'The Drupal userid of the submitee.'
+      ),
+      'class' => array(
+        'type' => 'varchar',
+        'length' => 256,
+        'not null' => TRUE,
+      ),
+      'job_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => FALSE,
+        'description' => 'The job ID if the loaded was was imported using a Tripal job.'
+      ),
+      'fid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => FALSE,
+        'description' => 'The file ID of the file to import. This only applies if the file was uploaded (i.e. not already on the server) and is mangaged by Drupal.'
+      ),
+      'arguments' => array(
+        'type' => 'text',
+        'size' => 'normal',
+        'not null' => FALSE,
+        'description' => 'Holds a serialized PHP array containing the key/value paris that are used for arguments of the job.'
+      ),
+      'submit_date' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => 'UNIX integer submit time'
+      ),
+    ),
+    'indexes' => array(
+      'class' => array('class'),
+    ),
+    'foreign keys' => array(
+      'tripal_jobs' => array(
+        'table' => 'tripal_jobs',
+        'columns' => array(
+          'job_id' => 'job_id',
+        ),
+      ),
+      'users' => array(
+        'table' => 'users',
+        'columns' => array(
+          'uid' => 'uid',
+        ),
+      ),
+      'file_managed' => array(
+        'table' => 'file_managed',
+        'columns' => array(
+          'fid' => 'fid',
+        ),
+      ),
+    ),
+    'primary key' => array('import_id'),
+  );
+}
 /**
  *
  * @return
@@ -771,7 +846,7 @@ function tripal_update_7300() {
       'not null' => TRUE
     );
     db_change_field('tripal_vocab', 'vocabulary', 'vocabulary',$spec);
-  } 
+  }
   catch (\PDOException $e) {
     $error = $e->getMessage();
     throw new DrupalUpdateException('Could not perform update: '. $error);
@@ -784,55 +859,28 @@ function tripal_update_7300() {
 function tripal_update_7301() {
   $transaction = db_transaction();
   try {
-    $schema['tripal_admin_notfications'] = array(
-      'description' => 'This table is used for information describing administrative
-       notifications. For example, when new fields are available.',
-      'fields' => array (
-        'note_id' => array (
-          'type' => 'serial',
-          'not null' => TRUE,
-        ),
-        'details' => array (
-          'description' => 'Description and additional information relating to the notification.',
-          'type' => 'text',
-          'not null' => TRUE,
-        ),
-        'title' => array (
-          'description' => 'Title of the notification.',
-          'type' => 'text',
-          'not null' => TRUE,
-        ),
-        'actions' => array (
-          'description' => 'Actions that can be performed on the notification, like disimissal or import.',
-          'type' => 'text',
-          'not null' => FALSE,
-        ),
-        'submitter_id' => array (
-          'description' => 'A unique id that should be specific to the notification to ensure notifications are not duplicated.',
-          'type' => 'text',
-          'not null' => TRUE,
-        ),
-        'enabled' => array (
-          'description' => 'Boolean indicating whether the notification is enabled or disabled (disabled will not be shown on the dashboard).',
-          'type' => 'int',
-          'not null' => TRUE,
-          'default' => 1,
-        ),
-        'type' => array (
-          'description' => 'Type of the notification, relating to what tripal function the notification belongs to, IE Fields, Jobs, Vocabulary.',
-          'type' => 'text',
-          'not null' => FALSE,
-        ),
-      ),
-        'primary key' => array (
-          0 => 'note_id',
-        ),
-    );
+    $schema['tripal_admin_notfications'] = tripal_tripal_admin_notifications_schema();
     db_create_table('tripal_admin_notfications', $schema['tripal_admin_notfications']);
   }
   catch (\PDOException $e) {
-      $transaction->rollback();
-      $error = $e->getMessage();
-      throw new DrupalUpdateException('Could not perform update: '. $error);
+    $transaction->rollback();
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
   }
 }
+
+/**
+ * Create new tripal import table.
+ */
+function tripal_update_7302() {
+  $transaction = db_transaction();
+  try {
+    $schema['tripal_import'] = tripal_tripal_import_schema();
+    db_create_table('tripal_import', $schema['tripal_import']);
+  }
+  catch (\PDOException $e) {
+    $transaction->rollback();
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}

+ 178 - 8
tripal/tripal.module

@@ -29,7 +29,7 @@ require_once "includes/TripalFields/TripalField.inc";
 require_once "includes/TripalFields/TripalFieldWidget.inc";
 require_once "includes/TripalFields/TripalFieldFormatter.inc";
 require_once "includes/TripalFieldQuery.inc";
-require_once "includes/TripalLoader/TripalLoader.inc";
+require_once "includes/TripalImporter.inc";
 
 /**
  * @defgroup tripal Tripal Core Module
@@ -326,6 +326,25 @@ function tripal_menu() {
     'type' => MENU_CALLBACK,
   );
 
+
+  // Add in the loaders
+  $importers = tripal_get_importers();
+  foreach ($importers as $class_name) {
+    tripal_load_include_importer_class($class_name);
+    if (class_exists($class_name)) {
+      $items['admin/tripal/loaders/' . $class_name::$machine_name] = array(
+        'title' => $class_name::$name,
+        'description' =>  $class_name::$description,
+        'page callback' => 'drupal_get_form',
+        'page arguments' => array('tripal_get_importer_form', $class_name),
+        'access arguments' => array('administer tripal'),
+        'type' => MENU_NORMAL_ITEM,
+        'file' => 'includes/tripal.importer.inc',
+        'file path' => drupal_get_path('module', 'tripal'),
+      );
+    }
+  }
+
   return $items;
 }
 
@@ -611,7 +630,7 @@ function TripalEntity_load($id, $reset = FALSE) {
 function tripal_import_api() {
   module_load_include('inc', 'tripal', 'api/tripal.d3js.api');
   module_load_include('inc', 'tripal', 'api/tripal.fields.api');
-  module_load_include('inc', 'tripal', 'api/tripal.loaders.api');
+  module_load_include('inc', 'tripal', 'api/tripal.importer.api');
   module_load_include('inc', 'tripal', 'api/tripal.terms.api');
   module_load_include('inc', 'tripal', 'api/tripal.entities.api');
   module_load_include('inc', 'tripal', 'api/tripal.files.api');
@@ -793,12 +812,12 @@ function tripal_block_configure ($delta = '') {
   switch ($delta) {
     case 'powered_by_tripal':
       $form['logo_size'] = array(
-      '#type' => 'radios',
-      '#title' => t('Logo Size'),
-      '#default_value' => variable_get('powered_by_tripal_size', 'small'),
-      '#options' => array(
-      'large' => t('Large'),
-      'small' => t('Small')
+        '#type' => 'radios',
+        '#title' => t('Logo Size'),
+        '#default_value' => variable_get('powered_by_tripal_size', 'small'),
+        '#options' => array(
+        'large' => t('Large'),
+        'small' => t('Small')
       ),
       '#description' => t('Select if you would like a small or large "Powered by Tripal" logo.'),
       );
@@ -830,4 +849,155 @@ function tripal_cron() {
     }
     watchdog('tripal_cron', 'tripal_cron ran');
   }
+}
+
+/**
+ * Implements hook_element_info().
+ *
+ * Used for creating new form API elements.
+ */
+function tripal_element_info() {
+
+  // Element for uploading large files.  This form element
+  // accepts the following keys when using in a form:
+  //   - #title:  The title that will appear above the element.
+  //   - #description:  The description that will appear below the element.
+  //   - #usage_type:  Required.  The type of file.  Thie will be stored in
+  //     the 'type' column of the file_usage table.
+  //   - #usage_id: Required. A unique numeric ID representing an entity, node
+  //     or some other record identifier.  This can be any identifier that
+  //     makes sense to the module that implements a form that uses this
+  //     element.
+  $elements['html5_file'] = array(
+    '#input' => 'TRUE',
+    '#process' => array('tripal_html5_file_process'),
+    '#element_validate' => array('tripal_html5_file_validate'),
+    '#value_callback' => 'tripal_html5_file_value',
+  );
+  return $elements;
+}
+
+/**
+ *  The process function for the html5_file form element.
+ */
+function tripal_html5_file_process($element, $form_state, $complete_form) {
+//   $nid = $element['#webform_component']['nid'];
+//   $form_key = $element['#webform_component']['form_key'];
+  $type = $element['#usage_id'] . '-' . $element['#usage_type'];
+  $name = $element['#name'];
+
+  $headers = array(
+    array('data' => 'File'),
+    array('data' => 'Size', 'width' => '10%'),
+    array('data' => 'Upload Progress', 'width' => '20%'),
+    array('data' => 'Action', 'width' => '10%')
+  );
+  $rows = array();
+  $table_vars = array(
+    'header'      => $headers,
+    'rows'        => $rows,
+    'attributes'  => array(
+      'class' => array('tripal-html5-file-upload-table'),
+      'id' => 'tripal-html5-file-upload-table-' . $type
+    ),
+    'sticky'      => TRUE,
+    'colgroups'   => array(),
+    'empty'       => t('There are currently no files.'),
+  );
+  $element['value'] = array(
+    '#type' => 'value',
+    '#value' => '',
+  );
+  $element['html5_file_table_key'] = array(
+    '#type' => 'hidden',
+    '#value' => $type,
+    '#attributes' => array(
+      'class' => array('tripal-html5-file-upload-table-key')
+    )
+  );
+  $element['html5_file_table'] = array(
+    '#type' => 'item',
+    '#title' => $element['#title'],
+    '#description' => $element['#description'],
+    '#markup' => theme('table', $table_vars)
+  );
+  $element[$name] = array(
+    '#type' => 'hidden',
+    '#attributes' => array('id' => 'tripal-html5-upload-fid-' . $type),
+    '#default_value' => $element['#value'],
+  );
+  $element['html5_file_submit'] = array(
+    '#type'     => 'submit',
+    '#value'    => 'Upload File',
+    '#name' => 'tripal_html5_file_upload_submit-' . $type,
+    // We don't want this button to submit as the file upload
+    // is handled by the JavaScript code.
+    '#attributes' => array(
+      'id' => 'tripal-html5-file-upload-submit-' . $type,
+      'onclick' => 'return (false);'
+    )
+  );
+
+  drupal_add_js(drupal_get_path ('module', 'tripal') . '/theme/js/TripalUploader.js');
+  drupal_add_js(drupal_get_path ('module', 'tripal') . '/theme/js/TripalUploadFile.js');
+  drupal_add_js(drupal_get_path ('module', 'tripal') . '/theme/js/tripal.file.js');
+
+  return $element;
+}
+/**
+ *
+ */
+function tripal_html5_file_validate($element, &$form_state) {
+  $is_required = $element['#required'];
+  $fid = $element['#value'];
+
+  if ($is_required and !$fid) {
+    form_error($element, t('A file must be uploaded.'));
+  }
+}
+
+
+
+/**
+ * Implements hook_handle_uplaoded_file().
+ */
+function tripal_handle_uploaded_file($filename, $filepath, $type) {
+
+  global $user;
+
+  // Split the type into a node ID and form_key
+  list($nid, $form_key) = explode('-', $type);
+
+  // See if this file is already managed then add another entry fin the
+  // usage table.
+  $fid = db_select('file_managed', 'fm')
+  ->fields('fm', array('fid'))
+  ->condition('uri', $filepath)
+  ->execute()
+  ->fetchField();
+  if ($fid) {
+    $file = file_load($fid);
+    file_usage_add($file, 'tripal', $form_key, $nid);
+    return $fid;
+  }
+
+  // Create a file object.
+  $file = new stdClass();
+  $file->uri = $filepath;
+  $file->filename = $filename;
+  $file->filemime = file_get_mimetype($filepath);
+  $file->uid = $user->uid;
+  $file->status = FILE_STATUS_PERMANENT;
+  $file = file_save($file);
+
+  return $file->fid;
+}
+
+/**
+ *
+ */
+function tripal_html5_file_value($element, $input = FALSE, &$form_state) {
+  if ($input) {
+    return $input;
+  }
 }

+ 97 - 133
tripal_chado/includes/TripalLoader/GFF3Loader.inc → tripal_chado/includes/TripalImporter/GFF3Importer.inc

@@ -1,19 +1,46 @@
 <?php
 
-class GFF3Loader extends TripalLoader {
+class GFF3Importer extends TripalImporter {
+
+ /**
+   * The name of this loader.  This name will be presented to the site
+   * user.
+   */
+  public static $name = 'Chado GFF3 File Loader';
 
   /**
-   * @see TripalLoader::form()
+   * The machine name for this loader. This name will be used to construct
+   * the URL for the loader.
+   */
+  public static $machine_name = 'gff3_loader';
+
+  /**
+   * A brief description for this loader.  This description will be
+   * presented to the site user.
+   */
+  public static $description = 'Import a GFF3 file into Chado';
+
+  /**
+   * An array containing the extensions of allowed file types.
+   */
+  public static $file_types = array('gff3');
+
+
+  /**
+   * Provides information to the user about the file upload.  Typically this
+   * may include a description of the file types allowed.
+   */
+  public static $upload_description = 'Please provide the GFF3 file. The file must have a .gff3 extension.';
+
+  /**
+   * The title that should appear above the upload button.
+   */
+  public static $upload_title = 'GFF3 File';
+  /**
+   * @see TripalImporter::form()
    */
   public function form($form, &$form_state) {
-    $form['gff_file']= array(
-      '#type'          => 'textfield',
-      '#title'         => t('GFF3 File'),
-      '#description'   => t('Please enter the full system path for the GFF file, or a path within the Drupal
-                           installation (e.g. /sites/default/files/xyz.gff).  The path must be accessible to the
-                           server on which this Drupal instance is running.'),
-      '#required' => TRUE,
-    );
+
     // get the list of organisms
     $sql = "SELECT * FROM {organism} ORDER BY genus, species";
     $org_rset = chado_query($sql);
@@ -232,70 +259,23 @@ class GFF3Loader extends TripalLoader {
        GFF file take precedence over those specified above."),
     );
 
-    $form['button'] = array(
-      '#type' => 'submit',
-      '#value' => t('Import GFF3 file'),
-      '#weight' => 10,
-    );
-
+    return $form;
   }
   /**
-   * @see TripalLoader::formSubmit()
+   * @see TripalImporter::formSubmit()
    */
   public function formSubmit($form, &$form_state) {
     global $user;
 
-    $gff_file = trim($form_state['values']['gff_file']);
-    $organism_id = $form_state['values']['organism_id'];
-    $add_only = $form_state['values']['add_only'];
-    $update   = $form_state['values']['update'];
-    $refresh  = 0; //$form_state['values']['refresh'];
-    $remove   = 0; //$form_state['values']['remove'];
-    $analysis_id = $form_state['values']['analysis_id'];
-    $use_transaction   = $form_state['values']['use_transaction'];
-    $target_organism_id = $form_state['values']['target_organism_id'];
-    $target_type = trim($form_state['values']['target_type']);
-    $create_target = $form_state['values']['create_target'];
-    $line_number   = trim($form_state['values']['line_number']);
-    $landmark_type   = trim($form_state['values']['landmark_type']);
-    $alt_id_attr   = trim($form_state['values']['alt_id_attr']);
-    $create_organism = $form_state['values']['create_organism'];
-    $re_mrna = trim($form_state['values']['re_mrna']);
-    $re_protein = trim($form_state['values']['re_protein']);
-
 
-    $args = array($gff_file, $organism_id, $analysis_id, $add_only,
-      $update, $refresh, $remove, $use_transaction, $target_organism_id,
-      $target_type, $create_target, $line_number, $landmark_type, $alt_id_attr,
-      $create_organism, $re_mrna, $re_protein);
-
-    $type = '';
-    if ($add_only) {
-      $type = 'import only new features';
-    }
-    if ($update) {
-      $type = 'import all and update';
-    }
-    if ($refresh) {
-      $type = 'import all and replace';
-    }
-    if ($remove) {
-      $type = 'delete features';
-    }
-    $fname = preg_replace("/.*\/(.*)/", "$1", $gff_file);
-    $includes = array(
-      module_load_include('inc', 'tripal_chado', 'includes/loaders/tripal_chado.gff_loader'),
-    );
-    tripal_add_job("$type GFF3 file: $fname", 'tripal_chado',
-        'tripal_feature_load_gff3', $args, $user->uid, 10, $includes);
 
     return '';
   }
   /**
-   * @see TripalLoader::formValidate()
+   * @see TripalImporter::formValidate()
    */
   public function formValidate($form, &$form_state) {
-    $gff_file = trim($form_state['values']['gff_file']);
+
     $organism_id = $form_state['values']['organism_id'];
     $target_organism_id = $form_state['values']['target_organism_id'];
     $target_type = trim($form_state['values']['target_type']);
@@ -312,20 +292,6 @@ class GFF3Loader extends TripalLoader {
     $re_mrna = trim($form_state['values']['re_mrna']);
     $re_protein = trim($form_state['values']['re_protein']);
 
-
-
-    // check to see if the file is located local to Drupal
-    $gff_file = trim($gff_file);
-    $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $gff_file;
-    if (!file_exists($dfile)) {
-      // if not local to Drupal, the file must be someplace else, just use
-      // the full path provided
-      $dfile = $gff_file;
-    }
-    if (!file_exists($dfile)) {
-      form_set_error('gff_file', t("Cannot find the file on the system. Check that the file exists or that the web server has permissions to read the file."));
-    }
-
     // @coder-ignore: there are no functions being called here
     if (($add_only AND ($update   OR $refresh  OR $remove)) OR
         ($update   AND ($add_only OR $refresh  OR $remove)) OR
@@ -355,36 +321,34 @@ class GFF3Loader extends TripalLoader {
   }
 
   /**
-   * @see TripalLoader::run()
+   * @see TripalImporter::run()
    */
-  public function run() {
-
-    // Perform parent validation
-    parent::run();
-
-    $gff_file = $this->job->arguments[0];
-    $organism_id = $this->job->arguments[1];
-    $analysis_id = $this->job->arguments[2];
-    $add_only = $this->job->arguments[3];
-    $update = $this->job->arguments[4];
-    $refresh = $this->job->arguments[5];
-    $remove = $this->job->arguments[6];
-    $use_transaction = $this->job->arguments[7];
-    $target_organism_id = $this->job->arguments[8];
-    $target_type = $this->job->arguments[9];
-    $create_target  = $this->job->arguments[10];
-    $start_line = $this->job->arguments[11];
-    $landmark_type = $this->job->arguments[12];
-    $alt_id_attr = $this->job->arguments[13];
-    $create_organism = $this->job->arguments[14];
-    $re_mrna = $this->job->arguments[15];
-    $re_protein = $this->job->arguments[16];
-
-    $this->tripal_feature_load_gff3($gff_file, $organism_id, $analysis_id,
-        $add_only = 0, $update = 1, $refresh = 0, $remove = 0, $use_transaction = 1,
-        $target_organism_id = NULL, $target_type = NULL,  $create_target = 0,
-        $start_line = 1, $landmark_type = '', $alt_id_attr = '',  $create_organism = FALSE,
-        $re_mrna = '', $re_protein = '', $job = NULL)
+  public function run($details, $job_id = NULL) {
+    $arguments = $details->arguments;
+
+    $gff_file = $arguments['file_path'];
+    $organism_id = $arguments['organism_id'];
+    $analysis_id = $arguments['analysis_id'];
+    $add_only = $arguments['add_only'];
+    $update = $arguments['update'];
+    $refresh = FALSE;
+    $remove = FALSE;
+    $use_transaction = $arguments['use_transaction'];
+    $target_organism_id = $arguments['target_organism_id'];
+    $target_type = $arguments['target_type'];
+    $create_target  = $arguments['create_target'];
+    $start_line = $arguments['line_number'];
+    $landmark_type = $arguments['landmark_type'];
+    $alt_id_attr = $arguments['alt_id_attr'];
+    $create_organism = $arguments['create_organism'];
+    $re_mrna = $arguments['re_mrna'];
+    $re_protein = $arguments['re_protein'];
+
+    $this->load($gff_file, $organism_id, $analysis_id,
+        $add_only, $update, $refresh, $remove, $use_transaction,
+        $target_organism_id, $target_type,  $create_target,
+        $start_line, $landmark_type, $alt_id_attr,  $create_organism,
+        $re_mrna, $re_protein, $job_id);
   }
 
   /**
@@ -463,7 +427,7 @@ class GFF3Loader extends TripalLoader {
    *
    * @ingroup gff3_loader
    */
-  private function tripal_feature_load_gff3($gff_file, $organism_id, $analysis_id,
+  private function load($gff_file, $organism_id, $analysis_id,
       $add_only = 0, $update = 1, $refresh = 0, $remove = 0, $use_transaction = 1,
       $target_organism_id = NULL, $target_type = NULL,  $create_target = 0,
       $start_line = 1, $landmark_type = '', $alt_id_attr = '',  $create_organism = FALSE,
@@ -600,7 +564,7 @@ class GFF3Loader extends TripalLoader {
             // we're done because this is a delete operation so break out of the loop.
             break;
           }
-          tripal_feature_load_gff3_fasta($fh, $interval, $num_read, $intv_read, $line_num, $filesize, $job);
+          $this->loadFasta($fh, $interval, $num_read, $intv_read, $line_num, $filesize, $job);
           continue;
         }
         // if the ##sequence-region line is present then we want to add a new feature
@@ -609,7 +573,7 @@ class GFF3Loader extends TripalLoader {
           $rstart = $region_matches[2];
           $rend = $region_matches[3];
           if ($landmark_type) {
-            tripal_feature_load_gff3_feature($organism, $analysis_id, $landmark_cvterm, $rid,
+            $this->loadFeature($organism, $analysis_id, $landmark_cvterm, $rid,
                 $rid, '', 'f', 'f', 1, 0);
           }
           continue;
@@ -896,7 +860,7 @@ class GFF3Loader extends TripalLoader {
         if ($update or $refresh or $add_only) {
 
           // Add/update the feature.
-          $feature = tripal_feature_load_gff3_feature($feature_organism, $analysis_id, $cvterm,
+          $feature = $this->loadFeature($feature_organism, $analysis_id, $cvterm,
               $attr_uniquename, $attr_name, $residues, $attr_is_analysis,
               $attr_is_obsolete, $add_only, $score);
 
@@ -923,58 +887,58 @@ class GFF3Loader extends TripalLoader {
             // add/update the featureloc if the landmark and the ID are not the same
             // if they are the same then this entry in the GFF is probably a landmark identifier
             if (strcmp($landmark, $attr_uniquename) !=0 ) {
-              tripal_feature_load_gff3_featureloc($feature, $organism,
+              $this->loadFeatureLoc($feature, $organism,
                   $landmark, $fmin, $fmax, $strand, $phase, $attr_fmin_partial,
                   $attr_fmax_partial, $attr_residue_info, $attr_locgroup);
             }
 
             // add any aliases for this feature
             if (array_key_exists('Alias', $tags)) {
-              tripal_feature_load_gff3_alias($feature, $tags['Alias']);
+              $this->loadAlias($feature, $tags['Alias']);
             }
             // add any dbxrefs for this feature
             if (array_key_exists('Dbxref', $tags)) {
-              tripal_feature_load_gff3_dbxref($feature, $tags['Dbxref']);
+              $this->loadDbxref($feature, $tags['Dbxref']);
             }
             // add any ontology terms for this feature
             if (array_key_exists('Ontology_term', $tags)) {
-              tripal_feature_load_gff3_ontology($feature, $tags['Ontology_term']);
+              $this->loadOntology($feature, $tags['Ontology_term']);
             }
             // add parent relationships
             if (array_key_exists('Parent', $tags)) {
-              tripal_feature_load_gff3_parents($feature, $cvterm, $tags['Parent'],
+              $this->loadParents($feature, $cvterm, $tags['Parent'],
                   $feature_organism->organism_id, $strand, $phase, $fmin, $fmax);
             }
 
             // add target relationships
             if (array_key_exists('Target', $tags)) {
-              tripal_feature_load_gff3_target($feature, $tags, $target_organism_id, $target_type, $create_target, $attr_locgroup);
+              $this->loadTarget($feature, $tags, $target_organism_id, $target_type, $create_target, $attr_locgroup);
             }
             // add gap information.  This goes in simply as a property
             if (array_key_exists('Gap', $tags)) {
               foreach ($tags['Gap'] as $value) {
-                tripal_feature_load_gff3_property($feature, 'Gap', $value);
+                $this->loadProperty($feature, 'Gap', $value);
               }
             }
             // add notes. This goes in simply as a property
             if (array_key_exists('Note', $tags)) {
               foreach ($tags['Note'] as $value) {
-                tripal_feature_load_gff3_property($feature, 'Note', $value);
+                $this->loadProperty($feature, 'Note', $value);
               }
             }
             // add the Derives_from relationship (e.g. polycistronic genes).
             if (array_key_exists('Derives_from', $tags)) {
-              tripal_feature_load_gff3_derives_from($feature, $cvterm, $tags['Derives_from'][0],
+              $this->loadDerivesFrom($feature, $cvterm, $tags['Derives_from'][0],
                   $feature_organism, $fmin, $fmax);
             }
             // add in the GFF3_source dbxref so that GBrowse can find the feature using the source column
             $source_ref = array('GFF_source:' . $source);
-            tripal_feature_load_gff3_dbxref($feature, $source_ref);
+            $this->loadDbxref($feature, $source_ref);
             // add any additional attributes
             if ($attr_others) {
               foreach ($attr_others as $tag_name => $values) {
                 foreach ($values as $value) {
-                  tripal_feature_load_gff3_property($feature, $tag_name, $value);
+                  $this->loadProperty($feature, $tag_name, $value);
                 }
               }
             }
@@ -1049,15 +1013,15 @@ class GFF3Loader extends TripalLoader {
               }
 
               // Add the new protein record.
-              $feature = tripal_feature_load_gff3_feature($organism, $analysis_id,
+              $feature = $this->loadFeature($organism, $analysis_id,
                   $protein_cvterm, $uname, $name, '', 'f', 'f', 1, 0);
               // Add the derives_from relationship.
               $cvterm = tripal_get_cvterm(array('cvterm_id' => $result->cvterm_id));
-              tripal_feature_load_gff3_derives_from($feature, $cvterm,
+              $this->loadDerivesFrom($feature, $cvterm,
                   $result->uniquename, $organism, $pfmin, $pfmax);
               // Add the featureloc record. Set the start of the protein to
               // be the start of the coding sequence minus the phase.
-              tripal_feature_load_gff3_featureloc($feature, $organism, $result->landmark,
+              $this->loadFeatureLoc($feature, $organism, $result->landmark,
                   $pfmin, $pfmax, $result->strand, '', 'f', 'f', '', 0);
             }
           }
@@ -1172,7 +1136,7 @@ class GFF3Loader extends TripalLoader {
    *
    * @ingroup gff3_loader
    */
-  private function tripal_feature_load_gff3_derives_from($feature, $cvterm, $object,
+  private function loadDerivesFrom($feature, $cvterm, $object,
       $organism, $fmin, $fmax) {
 
     $type = $cvterm->name;
@@ -1281,7 +1245,7 @@ class GFF3Loader extends TripalLoader {
    *
    * @ingroup gff3_loader
    */
-  private function tripal_feature_load_gff3_parents($feature, $cvterm, $parents,
+  private function loadParents($feature, $cvterm, $parents,
       $organism_id, $strand, $phase, $fmin, $fmax) {
 
     $uname = $feature->uniquename;
@@ -1388,7 +1352,7 @@ class GFF3Loader extends TripalLoader {
    *
    * @ingroup gff3_loader
    */
-  private function tripal_feature_load_gff3_dbxref($feature, $dbxrefs) {
+  private function loadDbxref($feature, $dbxrefs) {
 
     // iterate through each of the dbxrefs
     foreach ($dbxrefs as $dbxref) {
@@ -1480,7 +1444,7 @@ class GFF3Loader extends TripalLoader {
    *
    * @ingroup gff3_loader
    */
-  private function tripal_feature_load_gff3_ontology($feature, $dbxrefs) {
+  private function loadOntology($feature, $dbxrefs) {
 
     // iterate through each of the dbxrefs
     foreach ($dbxrefs as $dbxref) {
@@ -1558,7 +1522,7 @@ class GFF3Loader extends TripalLoader {
    *
    * @ingroup gff3_loader
    */
-  private function tripal_feature_load_gff3_alias($feature, $aliases) {
+  private function loadAlias($feature, $aliases) {
 
     // make sure we have a 'synonym_type' vocabulary
     $select = array('name' => 'synonym_type');
@@ -1715,7 +1679,7 @@ class GFF3Loader extends TripalLoader {
    *
    * @ingroup gff3_loader
    */
-  private function tripal_feature_load_gff3_feature($organism, $analysis_id, $cvterm, $uniquename,
+  private function loadFeature($organism, $analysis_id, $cvterm, $uniquename,
       $name, $residues, $is_analysis = 'f', $is_obsolete = 'f', $add_only, $score) {
 
     // Check to see if the feature already exists.
@@ -1842,7 +1806,7 @@ class GFF3Loader extends TripalLoader {
    *
    * @ingroup gff3_loader
    */
-  private function tripal_feature_load_gff3_featureloc($feature, $organism, $landmark, $fmin,
+  private function loadFeatureLoc($feature, $organism, $landmark, $fmin,
       $fmax, $strand, $phase, $is_fmin_partial, $is_fmax_partial, $residue_info, $locgroup,
       $landmark_type_id = '', $landmark_organism_id = '', $create_landmark = 0,
       $landmark_is_target = 0) {
@@ -2023,7 +1987,7 @@ class GFF3Loader extends TripalLoader {
    *
    * @ingroup gff3_loader
    */
-  private function tripal_feature_load_gff3_property($feature, $property, $value) {
+  private function loadProperty($feature, $property, $value) {
 
     // first make sure the cvterm exists.  if not, then add it
     $select = array(
@@ -2105,7 +2069,7 @@ class GFF3Loader extends TripalLoader {
    *
    * @ingroup gff3_loader
    */
-  private function tripal_feature_load_gff3_fasta($fh, $interval, &$num_read, &$intv_read, &$line_num, $filesize, $job) {
+  private function loadFasta($fh, $interval, &$num_read, &$intv_read, &$line_num, $filesize, $job) {
     print "\nLoading FASTA sequences\n";
     $residues = '';
     $id = NULL;
@@ -2196,7 +2160,7 @@ class GFF3Loader extends TripalLoader {
    *
    * @ingroup gff3_loader
    */
-  private function tripal_feature_load_gff3_target($feature, $tags, $target_organism_id, $target_type, $create_target, $attr_locgroup) {
+  private function loadTarget($feature, $tags, $target_organism_id, $target_type, $create_target, $attr_locgroup) {
     // format is: "target_id start end [strand]", where strand is optional and may be "+" or "-"
     $matched = preg_match('/^(.*?)\s+(\d+)\s+(\d+)(\s+[\+|\-])*$/', trim($tags['Target'][0]), $matches);
 
@@ -2318,7 +2282,7 @@ class GFF3Loader extends TripalLoader {
 
       // we want to add a featureloc record that uses the target feature as the srcfeature (landmark)
       // and the landmark as the feature.
-      tripal_feature_load_gff3_featureloc($feature, $organism, $target_feature, $target_fmin,
+      $this->loadFeatureLoc($feature, $organism, $target_feature, $target_fmin,
           $target_fmax, $target_strand, $phase, $attr_fmin_partial, $attr_fmax_partial, $attr_residue_info,
           $attr_locgroup, $t_type_id, $t_organism_id, $create_target, TRUE);
     }

+ 10 - 10
tripal_chado/tripal_chado.module

@@ -367,16 +367,16 @@ function tripal_chado_menu() {
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_NORMAL_ITEM,
   );
-  $items['admin/tripal/loaders/gff3_load'] = array(
-    'title' => 'Chado GFF3 File Loader',
-    'description' => 'Import a GFF3 file into Chado',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('tripal_feature_gff3_load_form'),
-    'access arguments' => array('administer tripal'),
-    'file' => 'includes/loaders/tripal_chado.gff_loader.inc',
-    'file path' => drupal_get_path('module', 'tripal_chado'),
-    'type' => MENU_NORMAL_ITEM,
-  );
+//   $items['admin/tripal/loaders/gff3_load'] = array(
+//     'title' => 'Chado GFF3 File Loader',
+//     'description' => 'Import a GFF3 file into Chado',
+//     'page callback' => 'drupal_get_form',
+//     'page arguments' => array('tripal_feature_gff3_load_form'),
+//     'access arguments' => array('administer tripal'),
+//     'file' => 'includes/loaders/tripal_chado.gff_loader.inc',
+//     'file path' => drupal_get_path('module', 'tripal_chado'),
+//     'type' => MENU_NORMAL_ITEM,
+//   );
   $items['admin/tripal/loaders/ncbi_taxonomy_loader'] = array(
     'title' => 'Chado NCBI Taxonomy Loader',
     'description' => 'Loads taxonomic details about installed organisms.',