Browse Source

Moving Tripal Galaxy quota system to core

Stephen Ficklin 7 years ago
parent
commit
63de05f31e

+ 64 - 0
tripal/api/tripal.files.api.inc

@@ -125,3 +125,67 @@ function tripal_get_files_stream($module_name = FALSE) {
 
   return $stream;
 }
+
+/**
+ * Formats a size (in bytes) in human readable format.
+ *
+ * Function taken from php.net
+ *
+ * @param $bytes
+ *   The size of the file in bytes
+ * @param $precision
+ *   The number of decimal places to use in the final number if needed
+ *
+ * @return string
+ *   A formatted string indicating the size of the file
+ */
+function tripal_format_bytes($bytes, $precision = 2) {
+  $units = ['B', 'KB', 'MB', 'GB', 'TB'];
+    
+  $bytes = max($bytes, 0);
+  $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
+  $pow = min($pow, count($units) - 1);
+    
+  // Uncomment one of the following alternatives
+  $bytes /= pow(1000, $pow);
+  // $bytes /= (1 << (10 * $pow));
+    
+  return round($bytes, $precision) . '' . $units[$pow];
+}
+
+/**
+ * Retrieves the list of files uploaded by a user.
+ * @param $uid
+ *   The ID of the user whose files should be retrieved.
+ * @param $allowed_types
+ *   A list of valid extensions to restrict the files to.
+ * @param $module
+ *   The name of the module that is managing the file.
+ *      
+ * @return
+ *   A list of file objects.
+ */
+function tripal_get_user_uploads($uid, $allowed_types = array(), $module = 'tripal') {
+  $user = user_load($uid);
+  
+  $query = db_select('file_managed', 'FM');
+  $query->fields('FM', array('fid'));
+  $query->distinct();
+  $query->condition('FM.uid', $user->uid);
+  $query->innerJoin('file_usage', 'FU', "FU.fid = FM.fid");
+  $query->condition('FU.module', $module);
+  $query->orderBy('FM.filename');
+  $files = $query->execute();
+  
+  $files_list = [];
+  while ($fid = $files->fetchField()) {
+    $file = file_load($fid);
+    foreach ($allowed_types as $type) {
+      if (preg_match('/\.' . $type . '$/', $file->filename)) {
+        $files_list[$fid] = $file;
+      }
+    }
+  }
+  
+  return $files_list;
+}

+ 3 - 5
tripal/api/tripal.importer.api.inc

@@ -22,10 +22,8 @@
  * 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 $file
+ *   The Drupal file object of the newly uploaded file.
  * @param $type
  *   The category or type of file.
  *
@@ -34,7 +32,7 @@
  *
  * @ingroup tripal_importer_api
  */
-function hook_handle_uploaded_file($filename, $filepath, $type) {
+function hook_handle_uploaded_file($file, $type) {
 
 }
 

+ 150 - 0
tripal/api/tripal.quotas.api.inc

@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * Retrieves the user's quote and default days to expire.
+ *
+ * @param $uid
+ *   The User ID.
+ *
+ * @return
+ *   An associative array containing the quota and default days to
+ *   expire.
+ */
+function tripal_get_user_quota($uid) {
+  $quota = db_select('tripal_custom_quota', 'tgcq')->fields('tgcq', [
+    'custom_quota',
+    'custom_expiration'
+  ])
+    ->condition('uid', $uid)
+    ->execute()
+    ->fetchObject();
+  if (! $quota) {
+    $quota = new stdClass();
+    $quota->custom_quota = variable_get('tripal_default_file_quota', pow(20, 6));
+    $quota->custom_expiration = variable_get('tripal_default_file_expiration', '60');
+  }
+  return $quota;
+}
+
+/**
+ * Sets a user's file space quota and default file expiration.
+ *
+ * @param $uid The
+ *          User ID for whom the quota will be set.
+ * @param $quota The
+ *          quota
+ * @param
+ *          $expriation
+ *          
+ * @return The inserted record.
+ */
+function tripal_set_user_quota($uid, $quota, $expriation) {
+  $values = [
+    'uid' => $uid,
+    'custom_quota' => $quota,
+    'custom_expiration' => $expriation
+  ];
+  return db_insert('tripal_custom_quota')->fields($values)->execute();
+}
+
+/**
+ * Removes a user's file space and default file expiration.
+ *
+ * @param $uid The
+ *          User ID for whom the quota will be removed.
+ *          
+ * @return
+ */
+function tripal_remove_user_quota($uid) {
+  db_delete('tripal_custom_quota')->condition('uid', $uid)->execute();
+}
+
+/**
+ * Retrieves the current size of all files uploaded by the user.
+ *
+ * @param $uid The
+ *          User ID.
+ *          
+ * @return The total number of bytes currently used.
+ */
+function tripal_get_user_usage($uid) {
+  // Get the user's current file usage
+  $sql = "
+    SELECT DISTINCT FU.fid
+    FROM {file_usage} FU
+      INNER JOIN {file_managed} FM ON FM.fid = FU.fid and FU.module = 'tripal'
+    WHERE FM.uid = :uid
+  ";
+  $fids = db_query($sql, [
+    ':uid' => $uid
+  ]);
+  $total_size = 0;
+  while ($fid = $fids->fetchObject()) {
+    $sql = "SELECT filesize FROM {file_managed} WHERE fid = :fid";
+    $total_size += db_query($sql, [
+      ':fid' => $fid->fid
+    ])->fetchObject()->filesize;
+  }
+  return $total_size;
+}
+
+/**
+ * Checks if a file needs to be expired.
+ */
+function tripal_expire_files(TripalJob $job = NULL) {
+  $results = db_select('tripal_expiration_files', 'tgfe')
+    ->fields('tgfe')
+    ->execute();
+  while ($result = $results->fetchObject()) {
+    if (time() > $result->expiration_date) {
+      
+      $file = file_load($result->fid);
+      if ($file) {
+        if ($job) {
+          $job->logMessage('File "' . $file->filename . '" has expired. Removing...');
+        }
+        // First remove the file from the file system.
+        file_delete($file, TRUE);
+        
+        // Remove the file from our file expiration table.
+        $query = db_delete('tripal_expiration_files');
+        $query->condition('fid', $result->fid);
+        $query->execute();
+      }
+    }
+  }
+}
+
+/**
+ * Resets the expiration data of a file managed by Tripal.
+ * 
+ * @param $fid
+ *   The file ID of the file to reset.
+ *   
+ * @return
+ *   TRUE on success, FALSE on failure.
+ */
+function tripal_reset_file_expiration($fid) {
+  
+  $file = file_load($fid);
+  try {
+    $quota = tripal_get_user_quota($file->uid);
+    $custom_expiration = $quota->custom_expiration;
+    $expiration_date = time() + $custom_expiration * 24 * 60 * 60;
+    
+    db_delete('tripal_expiration_files')
+       ->condition('fid', $fid)
+       ->execute();
+    db_insert('tripal_expiration_files')
+      ->fields([
+        'fid' => $file->fid,
+        'expiration_date' => $expiration_date,
+      ])
+      ->execute();  
+  }
+  catch (Exception $e) {
+    tripal_report_error('trp_quota', TRIPAL_ERROR, $e->getMessage());
+    return FALSE;
+  }
+  return TRUE;
+}

+ 33 - 7
tripal/includes/tripal.importer.inc

@@ -5,6 +5,7 @@
  * Build the form for a TripalImporter implementation.
  */
 function tripal_get_importer_form($form, &$form_state, $class) {
+  global $user;
 
   tripal_load_include_importer_class($class);
 
@@ -24,6 +25,20 @@ function tripal_get_importer_form($form, &$form_state, $class) {
   }
 
   if (array_key_exists('file_upload', $class::$methods) and $class::$methods['file_upload'] == TRUE) {
+    
+    $existing_files = tripal_get_user_uploads($user->uid, $class::$file_types);
+    if (count($existing_files) > 0) {
+      $fids = [0 => '--Select a file--'];
+      foreach ($existing_files as $fid => $file) {
+        $fids[$fid] = $file->filename . ' (' . tripal_format_bytes($file->filesize) . ') ';
+      }
+      $form['file']['file_upload_existing'] = [
+        '#type' => 'select',
+        '#title' => t('Existing Files'),
+        '#description' => t('You may select a file that is already uploaded.'),
+        '#options' => $fids,
+      ];
+    }
     $form['file']['file_upload']= array(
       '#type' => 'html5_file',
       '#title' => '',
@@ -102,7 +117,8 @@ function tripal_get_importer_form_validate($form, &$form_state) {
   $file_local = NULL;
   $file_upload = NULL;
   $file_remote = NULL;
-
+  $file_existing = NULL;
+  
   // Get the form values for the file.
   if (array_key_exists('file_local', $class::$methods) and $class::$methods['file_local'] == TRUE) {
     $file_local = trim($form_state['values']['file_local']);
@@ -123,15 +139,16 @@ function tripal_get_importer_form_validate($form, &$form_state) {
   }
   if (array_key_exists('file_upload', $class::$methods) and $class::$methods['file_upload'] == TRUE) {
     $file_upload = trim($form_state['values']['file_upload']);
+    if (array_key_exists('file_upload_existing', $form_state['values']) and $form_state['values']['file_upload_existing']) {
+      $file_existing = $form_state['values']['file_upload_existing'];
+    }
   }
   if (array_key_exists('file_remote', $class::$methods) and $class::$methods['file_remote'] == TRUE) {
     $file_remote = trim($form_state['values']['file_remote']);
   }
 
-
-
   // The user must provide at least an uploaded file or a local file path.
-  if ($class::$file_required == TRUE and !$file_upload and !$file_local and !$file_remote) {
+  if ($class::$file_required == TRUE and !$file_upload and !$file_local and !$file_remote and !$file_existing) {
     form_set_error('file_local', t("You must provide a file."));
   }
 
@@ -154,6 +171,7 @@ function tripal_get_importer_form_submit($form, &$form_state) {
   // full file path and the fid instead.
   unset($run_args['file_local']);
   unset($run_args['file_upload']);
+  unset($run_args['file_upload_existing']);
   unset($run_args['form_build_id']);
   unset($run_args['form_token']);
   unset($run_args['form_id']);
@@ -163,6 +181,7 @@ function tripal_get_importer_form_submit($form, &$form_state) {
   $file_local = NULL;
   $file_upload = NULL;
   $file_remote = NULL;
+  $file_existing = NULL;
 
   // Get the form values for the file.
   if (array_key_exists('file_local', $class::$methods) and $class::$methods['file_local'] == TRUE) {
@@ -170,23 +189,30 @@ function tripal_get_importer_form_submit($form, &$form_state) {
   }
   if (array_key_exists('file_upload', $class::$methods) and $class::$methods['file_upload'] == TRUE) {
     $file_upload = trim($form_state['values']['file_upload']);
+    if (array_key_exists('file_upload_existing', $form_state['values']) and $form_state['values']['file_upload_existing']) {
+      $file_existing = trim($form_state['values']['file_upload_existing']);
+    }
   }
   if (array_key_exists('file_remote', $class::$methods) and $class::$methods['file_remote'] == TRUE) {
     $file_remote = trim($form_state['values']['file_remote']);
   }
 
+
   // Sumbit a job for this loader.
   $fname = '';
   $fid = NULL;
   $file_details = array();
-  if ($file_local) {
+  if ($file_existing) {
+    $file_details['fid'] = $file_existing;
+  }
+  elseif ($file_local) {
     $fname = preg_replace("/.*\/(.*)/", "$1", $file_local);
     $file_details['file_local'] = $file_local;
   }
-  if ($file_upload) {
+  elseif ($file_upload) {
     $file_details['fid'] = $file_upload;
   }
-  if ($file_remote) {
+  elseif ($file_remote) {
     $file_details['file_remote'] = $file_remote;
   }
   try {

+ 81 - 25
tripal/includes/tripal.upload.inc

@@ -19,6 +19,38 @@ function tripal_file_upload($type, $filename, $action = NULL, $chunk = 0) {
     ));
     return;
   }
+  
+  // Make sure we don't go over the user's quota, but only do this check
+  // before loading the first chunk so we don't repeat it over and over again.
+  if ($action == 'check' and $chunk == 0) {
+    $usage = tripal_get_user_usage($user->uid);
+    $quota = tripal_get_user_quota($user->uid);
+    $quota_size = $quota->custom_quota;
+    if ($file_size + $usage > $quota_size) {
+      drupal_json_output(array(
+        'status' => 'failed',
+        'message' => t("Unfortunately, you can not upload this file as the size exceeds the remainder of your quota. See your account page under the 'Uploads' tab to manage your uploaded files."),
+        'file_id' => '',
+      ));
+      return;
+    }
+    
+    // Make sure we don't go over the max file upload size.
+    $upload_max = variable_get('tripal_upload_max_size', 10000000000);
+    if ($file_size > $upload_max) {
+      $message = t("Unfortunately, you can not upload this file as the size exceeds the the maximum file size allowed by this site: " . tripal_format_bytes($upload_max) . '. ');
+      
+      if (user_access('administer tripal')) {
+        $message .= t('You can manage the file upload by visiting: Home » Administration » Tripal » User File Management.');
+      }
+      drupal_json_output(array(
+        'status' => 'failed',
+        'message' => $message,
+        'file_id' => '',
+      ));
+      return;
+    }
+  }
 
   // Allow the module that will own the file to make some checks. The module
   // is allowed to stop the upload as needed.
@@ -42,7 +74,7 @@ function tripal_file_upload($type, $filename, $action = NULL, $chunk = 0) {
   }
 
   switch ($action) {
-    // If the action is 'put' then the callee is sending a chunk of the file
+    // If the action is 'save' then the callee is sending a chunk of the file
     case 'save':
       tripal_file_upload_put($filename, $chunk, $user_dir);
       break;
@@ -56,7 +88,6 @@ function tripal_file_upload($type, $filename, $action = NULL, $chunk = 0) {
 }
 /**
  * Merges all chunks into a single file
- * @param unknown $filename
  */
 function tripal_file_upload_merge($filename, $type, $user_dir) {
   global $user;
@@ -127,33 +158,58 @@ function tripal_file_upload_merge($filename, $type, $user_dir) {
     $status = 'failed';
     $message = 'Merge file is missing after upload ' . $merge_file . '.';
   }
+  
   $file_id = NULL;
 
-  // If the file has been successfully merged then let the calling module
-  // deal with it.
-  if ($status != 'failed') {
-    $function = $module . '_handle_uploaded_file';
-    if(function_exists($function)) {
-      $file_id = $function($filename, $merge_file, $type);
-      if ($file_id) {
-        $file = file_load($file_id);
-        $status = 'completed';
-        $full_path = drupal_realpath($file->uri);
-        $md5sum = md5_file($full_path);
-        $md5sum_file = fopen("$full_path.md5", "w");
-        fwrite($md5sum_file, $md5sum);
-        fclose($md5sum_file);
-        unlink($temp_dir);
-      }
-      else {
-        $status = 'failed';
-        $message = 'Could not add file to the database.';
-      }
+  // If the file has been successfully merged then do a few other things...
+  if ($status != 'failed') {   
+    
+    // See if this file is already managed if so, then it has been uploaded
+    // before and we don't need to add a managed item again.
+    $fid = db_select('file_managed', 'fm')
+      ->fields('fm', ['fid'])
+      ->condition('uri', $merge_file)
+      ->execute()
+      ->fetchField();
+    
+    // Add the file if it is not already managed.
+    if (!$fid) {
+      $file = new stdClass();
+      $file->uri = $merge_file;
+      $file->filename = $filename;
+      $file->filemime = file_get_mimetype($merge_file);
+      $file->uid = $user->uid;
+      $file->status = FILE_STATUS_PERMANENT;
+      $file = file_save($file);
+      $fid = $file->fid;
     }
-    else {
-      $status = 'failed';
-      $message = 'Cannot find the function: ' . $function . '().';
+    
+    // Reload the file object to get a full object.
+    $file_id = $fid;
+    $file = file_load($fid);
+    
+    // Set the file as being managed by Tripal.
+    file_usage_add($file, 'tripal', $type, 0);
+    
+    // Set the file expiration.
+    tripal_reset_file_expiration($fid);
+    
+    // Generate an md5 file the uploaded file.
+    $full_path = drupal_realpath($file->uri);
+    $md5sum = md5_file($full_path);
+    $md5sum_file = fopen("$full_path.md5", "w");
+    fwrite($md5sum_file, $md5sum);
+    fclose($md5sum_file);
+    
+    // Remove the temporary directory.
+    file_unmanaged_delete_recursive($temp_dir);
+    
+    // Now let the submitting module deal with it.
+    $function = $module . '_handle_uploaded_file';
+    if (function_exists($function)) {
+      $function($file, $type);
     }
+    $status = 'completed';
   }
 
   if ($status == 'failed') {

+ 328 - 0
tripal/includes/tripal.user.inc

@@ -0,0 +1,328 @@
+<?php
+
+
+/**
+ * Provides the page with a list of files uploaded by the user.
+ *
+ * @param $uid
+ *   The user ID.
+ *
+ * @return
+ *   A Drupal render array.
+ */
+ function tripal_user_files_page($uid) {
+
+   // Get all of the files that have been uploaded by the user.
+   // TODO: we should make this a paged query in case a user has a huge
+   // numbef of uploaded files.
+   $sql = "
+     SELECT FM.fid, FM.filename, TGEF.expiration_date
+     FROM {file_managed} FM
+       INNER JOIN {file_usage} FU on FM.fid = FU.fid and FM.uid = :user_id
+       LEFT JOIN {tripal_expiration_files} TGEF on TGEF.fid = FU.fid
+     WHERE FU.module = 'tripal'
+     GROUP BY FM.fid, TGEF.expiration_date
+     ORDER BY FM.filename
+   ";
+   $files = db_query($sql, array(':user_id' => $uid));
+   $rows = array();
+   While ($entry = $files->fetchObject()) {
+    $file = file_load($entry->fid);
+
+    // Don't list files that don't exist on the file system.
+    if (!file_exists($file->uri)) {
+      continue;
+    }
+
+    $date_uploaded = date('Y-m-d H:i:s', $file->timestamp);
+    $expiration = $entry->expiration_date ? date('Y-m-d H:i:s', $entry->expiration_date) : '';
+    $actions = l('Delete', "user/$uid/files/$file->fid/delete") . ' | ' .
+               l('Renew', "user/$uid/files/$file->fid/renew");
+
+    $rows[] = array(
+      $entry->fid,
+      l($file->filename,"/user/$uid/files/$file->fid"),
+      $date_uploaded,
+      $expiration,
+      tripal_format_bytes($file->filesize),
+      $actions,
+    );
+  }
+  $header = array('ID', 'File Name', 'Upload Date', 'Expiration', 'Size', 'Actions');
+
+  // Get the user quota settings.
+  $quota = tripal_get_user_quota($uid);
+  $usage = tripal_get_user_usage($uid);
+
+  $content = array(
+    'page_title' => array(
+      '#type' => 'markup',
+      '#markup' => '<h2>Your Uploaded Files</h2>',
+    ),
+    'page_description' => array(
+      '#type' => 'markup',
+      '#markup' => '<p>' . t('Each user is allowed to consume a limited amount of space with uploaded files. This page provides details about your current usage, your limits and files you\'ve uploaded.') . '</p>',
+    ),
+    'usage' => array(
+      '#type' => 'item',
+      '#title' => 'Current Usage',
+      '#markup' => tripal_format_bytes($usage),
+      '#description' => t('The total number of bytes you currently consume.'),
+    ),
+    'quota' => array(
+      '#type' => 'item',
+      '#title' => 'Current Quota',
+      '#markup' => tripal_format_bytes($quota->custom_quota),
+      '#description' => t('The maximum number of bytes of files you can upload.')
+    ),
+    'expiration' => array(
+      '#type' => 'item',
+      '#title' => 'Current Days to Expire',
+      '#markup' => $quota->custom_expiration,
+      '#description' => t('The number of days a file will remain on the server before deletion. The expiration of date of a file can be renewed using the "Renew" link in the table below.')
+    ),
+    'file_list' => array(
+      '#type' => 'item',
+      '#title' => 'Uploaded Files',
+      '#markup' => theme_table(array(
+        'header' => $header,
+        'rows' => $rows,
+        'attributes' => array(),
+        'caption' => t('Click a file name for more details.'),
+        'colgroups' => array(),
+        'sticky' => TRUE,
+        'empty' => 'You currently have no uploaded files.',
+      )),
+    )
+  );
+
+
+  if ($usage < $quota->custom_quota) {
+    drupal_set_message('Your file usage is currently below the file quota limit.');
+  }
+  else {
+    drupal_set_message('Your file usage is currently over your file quota limit. Please remove some files before uploading more', 'warning');
+  }
+
+  return $content;
+}
+
+/**
+ * User action to renew the expiration of a file.
+ *
+ * Adds the current time and the expiration date (either from default or if
+ * the user has a custom expiration date) to tripal_expiration_files
+ * table.
+ *
+ **/
+function tripal_renew_file($fid) {
+  $file = file_load($fid);
+  $success = tripal_reset_file_expiration($fid);
+
+  if ($success) {
+    drupal_set_message('Successfully updated expiration date.');
+  }
+  drupal_goto('user/' . $file->uid . '/files/');
+}
+
+/**
+ * Downloads a file.
+ *
+ * @param $fid
+ *   The File ID of the file to be downloaded.
+ */
+function tripal_download_file($fid) {
+  $file = file_load($fid);
+  if (file_exists($file->uri)) {
+    $headers = array();
+    $headers['Content-Type'] = $file->filemime;
+    $headers['Content-Disposition']  = 'attachment; filename=' . $file->filename;
+    $headers['Content-Length'] = $file->filesize;
+    file_transfer($file->uri, $headers);
+  }
+  else {
+    drupal_set_message('Can not download. The file no longer exists on the server.', 'error');
+    drupal_goto('user/' . $file->uid . '/files/');
+  }
+}
+
+/**
+ * Provides a confirmation form for deleting a galaxy workflow uploaded file.
+ */
+function tripal_delete_file_form($form, $form_state, $uid, $fid) {
+  $form = array();
+
+  $file = file_load($fid);
+
+  $form['uid'] = array(
+    '#type' => 'value',
+    '#value' => $uid,
+  );
+  $form['fid'] = array(
+    '#type' => 'value',
+    '#value' => $fid,
+  );
+
+  return confirm_form($form,
+      t('Confirm deletion of the file named "' . $file->filename . '"?'),
+      'user/' . $uid . '/files/',
+      t('Warning.  If this file is intended to be used with a analysis workflow submission that has not yet started then the workflow will fail. Once deleted, the file can no longer be used for new workflow submissions without uploading again.')
+  );
+}
+
+/**
+ * Implements a form submit for deleting a galaxy workflow uploaded file.
+ */
+function tripal_delete_file_form_submit($form, &$form_state) {
+  $fid = $form_state['values']['fid'];
+  $uid = $form_state['values']['uid'];
+  $file = file_load($fid);
+
+  // Remove the file from the file_usage table for all entries that link
+  // to the tripal module.
+  file_usage_delete($file, 'tripal', NULL, NULL, 0);
+
+  // Get any remaining usage for other modules
+  $file_usage = file_usage_list($file);
+
+  // If this file is still used by the tripal module then something
+  // didn't work right.
+  if (in_array('tripal', $file_usage)) {
+    drupal_set_message('The file could not be removed.  Please contact the site administrator.', 'error');
+  }
+
+  // If there is no other usage of this file from other modules then delete it.
+  if (count(array_keys($file_usage)) == 0) {
+    if (file_unmanaged_delete($file->uri)) {
+
+      // Also remove the md5 checksum.
+      if (file_exists(file_unmanaged_delete($file->uri . '.md5'))) {
+        file_unmanaged_delete($file->uri . '.md5');
+      }
+      drupal_set_message('The file has been fully removed.');
+    }
+    else {
+      drupal_set_message('The file has removed from this list and does not count against your quota, but other components of this site rely on this file. Thus it has not been fully removed.');
+    }
+  }
+  drupal_goto('user/' . $file->uid . '/files/');
+}
+
+/**
+ * Provides details about a file.
+ */
+function tripal_view_file($uid, $fid) {
+  $file = file_load($fid);
+
+  $headers = array();
+  $rows = array();
+
+  $actions = l('Delete', "user/$uid/files/$file->fid/delete") . '<br>' .
+             l('Download', "user/$uid/files/$file->fid/download");
+
+  // Name row
+  $rows[] = array(
+    array(
+      'data' => 'File Name',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    $file->filename
+  );
+
+  $date_uploaded = date('Y-m-d H:i:s', $file->timestamp);
+  $rows[] = array(
+    array(
+      'data' => 'Upload Date',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    $date_uploaded
+  );
+
+  $expiration_date = db_select('tripal_expiration_files', 'tgef')
+    ->fields('tgef', array('expiration_date'))
+    ->condition('fid', $fid)
+    ->execute()
+    ->fetchField();
+  $expiration = $expiration_date ? date('Y-m-d H:i:s', $expiration_date) : '';
+  $rows[] = array(
+    array(
+      'data' => 'Expiration Date',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    $expiration
+  );
+
+  // Find which workflow submissions are using this file.
+  $usage = file_usage_list($file);
+  $usage = $usage['tripal'];
+  $workflow_nids = array();
+  foreach ($usage as $step => $nid) {
+    $nid = array_keys($nid)[0];
+    if (!in_array($nid, $workflow_nids)) {
+      $workflow_nids[] = $nid;
+    }
+  }
+  $wf_links = array();
+  foreach ($workflow_nids as $i => $nid) {
+    $query = db_select('tripal_workflow', 'tgw');
+    $query->fields('tgw', array('workflow_name'));
+    $query->innerJoin('tripal_workflow_submission', 'tgws', 'tgw.galaxy_workflow_id = tgws.galaxy_workflow_id');
+    $query->fields('tgws', array('sid'));
+    $query->condition('tgw.nid', $nid, '=');
+    $results = $query->execute();
+    $workflow = $results->fetchObject();
+    if ($workflow) {
+      $wf_links[] = l($workflow->workflow_name . ' (submission ID: ' . $workflow->sid . ')', "user/$uid/galaxy-jobs/$workflow->sid");
+    }
+  }
+  $rows[] = array(
+    array(
+      'data' => 'Usage',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    theme_item_list(array(
+      'items' => $wf_links,
+      'title' => '',
+      'type' => 'ul',
+      'attributes' => array(),
+    )),
+  );
+  $rows[] = array(
+    array(
+      'data' => 'Actions',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    $actions
+  );
+
+  $content = array(
+    'description' => array(
+      '#type' => 'markup',
+      '#markup' => '<p>' . t('The following file has been uploaded for use in an analytical workflow.') . '</p>',
+    ),
+    'return' => array(
+      '#type' => 'markup',
+      '#markup' => '<p>' . l('View all Uploaded Files', "user/$uid/files") . '</p>',
+    ),
+    'file_details' => array(
+      '#type' => 'markup',
+      '#markup' => theme_table(array(
+        'header' => $headers,
+        'rows' => $rows,
+        'attributes' => array(),
+        'sticky' => FALSE,
+        'caption' => '',
+        'colgroups' => array(),
+        'empty' => '',
+      )),
+    ),
+  );
+  return $content;
+}
+
+

+ 84 - 0
tripal/tripal.install

@@ -154,6 +154,8 @@ function tripal_schema() {
   $schema['tripal_jobs'] = tripal_tripal_jobs_schema();
   $schema['tripal_token_formats'] = tripal_tripal_token_formats_schema();
   $schema['tripal_variables'] = tripal_tripal_variables_schema();
+  $schema['tripal_custom_quota'] = tripal_tripal_custom_quota_schema();
+  $schema['tripal_expiration_files'] = tripal_tripal_expiration_files_schema();
 
 
   // Adds a table for managing TripalEntity entities.
@@ -834,6 +836,71 @@ function tripal_tripal_bundle_variables_schema() {
   return $schema;
 }
 
+/**
+ * Provides the schema for the tripal_custom_quota table.
+ */
+function tripal_tripal_custom_quota_schema() {
+  $schema = array(
+    'table' => 'tripal_custom_quota',
+    'fields' => array(
+      'uid' => array(
+        'type' => 'int',
+        'size' => 'big',
+        'not NULL' => TRUE,
+      ),
+      'custom_quota' => array(
+        'type' => 'int',
+        'size' => 'big',
+        'not NULL' => TRUE,
+      ),
+      'custom_expiration' => array(
+        'type' => 'int',
+        'size' => 'big',
+        'not NULL' => TRUE,
+      ),
+    ),
+    'primary key' => array('uid'),
+    'unique keys' => array(
+      'tripal_custom_quota_uq1' => array('uid'),
+    ),
+    'indexes' => array(
+      'tripal_custom_quota_idx1' => array('uid'),
+    ),
+  );
+  return $schema;
+}
+
+/**
+ * Provides the schema for the tripal_expiration_files table.
+ */
+function tripal_tripal_expiration_files_schema() {
+  $schema = array (
+    'table' => 'tripal_expiration_files',
+    'fields' => array (
+      'fid' => array(
+        'type' => 'int',
+        'not NULL' => TRUE
+      ),
+      'expiration_date' => array(
+        'type' => 'int',
+        'size' => 'big',
+        'not NULL' => TRUE
+      ),
+    ),
+    'primary key' => array(
+      0 => 'fid'
+    ),
+    'unique keys' => array(
+      'tripal_expiration_files_uq1' => array('fid'),
+    ),
+    'indexes' => array(
+      'tripal_expiration_files_idx1' => array('fid'),
+    ),
+  );
+  return $schema;
+}
+
+
 /**
  * Additional Tripal Admin Notification Information.
  *
@@ -1074,4 +1141,21 @@ function tripal_update_7309() {
     $error = $e->getMessage();
     throw new DrupalUpdateException('Could not add the tripal_collection table:' . $error);
   }
+}
+
+/**
+ * Adds support for file quotas.
+ */
+function tripal_update_7310() {
+  try {
+    $schema = array();
+    $schema['tripal_custom_quota'] = tripal_tripal_custom_quota_schema();
+    $schema['tripal_expiration_files'] = tripal_tripal_expiration_files_schema();
+    db_create_table('tripal_custom_quota', $schema['tripal_custom_quota']);
+    db_create_table('tripal_expiration_files', $schema['tripal_expiration_files']);
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
 }

+ 233 - 61
tripal/tripal.module

@@ -424,10 +424,213 @@ function tripal_menu() {
     'file' => 'includes/tripal.admin.inc',
     'file path' => drupal_get_path('module', 'tripal'),
   );
-
+  
+  
+  //
+  // USER FILE MANAGEMENT
+  //
+  $items['admin/tripal/files'] = [
+    'title' => 'User File Management',
+    'description' => 'Set maximum upload sizes, quotas and view usage.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_admin_manage_files_form'],
+    'access arguments' => ['administer tripal'],
+    'type' => MENU_NORMAL_ITEM,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+    'weight' => 30,
+  ];
+  
+  $items['admin/tripal/files/quota'] = [
+    'title' => 'User Quotas',
+    'description' => 'Set default quota, expiration date, and custom quotas',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_admin_manage_quota_form'],
+    'access arguments' => ['administer tripal'],
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+    'weight' => 10,
+  ];
+  
+  // Admin remove user quota
+  $items['admin/tripal/files/quota/remove/%'] = [
+    'title' => 'Remove custom user quota',
+    'description' => "Revert's a user's quota and expiration of files to the site wide defaults.",
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_admin_remove_quota_form', 5],
+    'access arguments' => ['administer tripal'],
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  
+  // Add user quota
+  $items['admin/tripal/files/quota/add'] = [
+    'title' => 'Add Custom Quota',
+    'description' => 'Gives the user a new quota and expiration date',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_admin_add_custom_form'],
+    'access arguments' => ['administer tripal'],
+    // TODO: Ask Stephen is this is fine as a link at the top of the form
+    // as well as a link in the table
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  
+  // Autocomplete path for the users on the site
+  $items['admin/tripal/files/quota/user/autocomplete'] = [
+    'title' => 'Autocomplete for existing users',
+    'description' => 'Provide a list of existing users on the site.',
+    'page callback' => 'tripal_users_autocomplete',
+    'access arguments' => ['administer tripal'],
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  
+  // Edit user quota
+  $items['admin/tripal/files/quota/edit/%'] = [
+    'title' => 'Edit Custom Quota',
+    'description' => 'Edit an existing user\'s quota and/or expiration date.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_admin_edit_quota_form', 5],
+    'access arguments' => ['administer tripal'],
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  
+  $items['admin/tripal/files/usage'] = [
+    'title' => 'File Usage',
+    'description' => 'Set default quota, expiration date, and custom quotas',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_admin_file_usage_page'],
+    'access arguments' => ['administer tripal'],
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+    'weight' => 15
+  ];
+
+  
+  //
+  // USER FILES
+  //
+  
+  // User view quota (Tab)
+  $items['user/%/files'] = [
+    'title' => 'Uploads',
+    'description' => 'Monitors what files that have been uploaded by user through the tripal module',
+    'page callback' => 'tripal_user_files_page',
+    'page arguments' => [1],
+    'access callback' => 'tripal_access_user_uploads',
+    'access arguments' => ['view', 1],
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'includes/tripal.user.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+    'weight' => 10,
+  ];
+  
+  $items['user/%/files/%'] = [
+    'title' => 'File Details',
+    'description' => "View details about the file",
+    'page callback' => 'tripal_view_file',
+    'page arguments' => [1, 3],
+    'access callback' => 'tripal_access_user_uploads',
+    'access arguments' => ['renew', 1, 3],
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.user.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  // User file renew.
+  $items['user/%/files/%/renew'] = [
+    'title' => 'Renew File',
+    'description' => "Renew a user's file",
+    'page callback' => 'tripal_renew_file',
+    'page arguments' => [3],
+    'access callback' => 'tripal_access_user_uploads',
+    'access arguments' => ['renew', 1, 3],
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.user.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  // User file download.
+  $items['user/%/files/%/download'] = [
+    'title' => 'Download File',
+    'description' => "Download a user's file based off of the link clicked in the table",
+    'page callback' => 'tripal_download_file',
+    'page arguments' => [3],
+    'access arguments' => ['download', 1, 3],
+    'access callback' => 'tripal_access_user_uploads',
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.user.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  // User file delete.
+  $items['user/%/files/%/delete'] = [
+    'title' => 'Delete File',
+    'description' => "Delete a user's file based on either user action or expired file",
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_delete_file_form', 1, 3],
+    'access callback' => 'tripal_access_user_uploads',
+    'access arguments' => ['delete', 1, 3],
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.user.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
   return $items;
 }
 
+/**
+ * Checks if the current user has permissions to perform an action on a file.
+ *
+ * @param $op
+ *   The operation to perform.  These include 'view', 'download', 'renew' and 
+ *   'delete'
+ * @param $uid
+ *   The user ID of the user's account that owns the file.
+ * @param $fid
+ *   The file ID.
+ */
+function tripal_access_user_uploads($op, $uid, $fid = NULL) {
+  global $user;
+  
+  // The site admin can do anything.
+  if (in_array('administrator', $user->roles)) {
+    return TRUE;
+  }
+  
+  // Only the user that owns the files can see them.
+  if ($uid != $user->uid) {
+    return FALSE;
+  }
+  
+  // If no file ID is provided and the user wants to view then
+  // this is the case where the user wants to see all the files.
+  if (!$fid and $op == 'view') {
+    return TRUE;
+  }
+    
+  $file = file_load($fid);
+  switch ($op) {
+    case 'view':
+    case 'download':
+    case 'renew':
+    case 'delete':
+      if ($user->uid == $file->uid) {
+        return TRUE;
+      }
+      break;
+  }
+  return FALSE;
+}
+/**
+ * An access function for data collections.
+ * 
+ * @return boolean
+ */
 function tripal_accesss_user_collections($uid) {
   if (!tripal_access_user_data($uid)) {
     return FALSE;
@@ -439,6 +642,24 @@ function tripal_accesss_user_collections($uid) {
   return TRUE;
 }
 
+/**
+ * Autocomplete function for listing existing users on the site.
+ *
+ * @return json array of users that match the query in the textfield
+ **/
+function tripal_users_autocomplete($string) {
+  $matches = [];
+  $result = db_select('users', 'u')
+    ->fields('u', ['name'])
+    ->condition('name', '%' . db_like($string) . '%', 'LIKE')
+    ->execute();
+  
+  foreach ($result as $row) {
+    $matches[$row->name] = check_plain($row->name);
+  }
+  drupal_json_output($matches);
+}
+
 /**
  * Access callback for accessing a user's Tripal-added private data.
  *
@@ -770,6 +991,7 @@ function tripal_import_api() {
   module_load_include('inc', 'tripal', 'api/tripal.variables.api');
   module_load_include('inc', 'tripal', 'api/tripal.upload.api');
   module_load_include('inc', 'tripal', 'api/tripal.collections.api');
+  module_load_include('inc', 'tripal', 'api/tripal.quotas.api');
   module_load_include('inc', 'tripal', 'api/tripal.DEPRECATED.api');
 }
 
@@ -1105,37 +1327,25 @@ function tripal_block_configure($delta = '') {
 function tripal_cron() {
 
   // Add jobs to the Tripal queue for commong tasks.
-  $args = array();
-  $includes = array();
+  $args = [];
+  $includes = [];
 
   if (variable_get('tripal_admin_notification_creation_during_cron', TRUE)) {
     $modules = module_implements('tripal_cron_notification');
     foreach ($modules as $module) {
       $function = $module . '_tripal_cron_notification';
-      tripal_add_job(
-        "Cron: Checking for '$module' notifications.",    // Job Name
-        'tripal',                                         // Module Name
-        $function,                                        // Callback
-        $args,                                            // Arguements
-        1,                                                // User UID
-        1,                                                // Priority (1-10)
-        $includes,                                        // Includes
-        TRUE                                              // Ignore Duplicates
-      );
+      tripal_add_job("Cron: Checking for '$module' notifications.", 'tripal',                                         // Module Name
+        $function, $args, 1, 1, $includes, TRUE);
     }
   }
 
   // Check for expired collections.
-  tripal_add_job(
-    'Cron: Checking expired collections',             // Job Name
-    'tripal',                                         // Module Name
-    'tripal_expire_collections',                      // Callback
-    $args,                                            // Arguements
-    1,                                                // User UID
-    1,                                                // Priority (1-10)
-    $includes,                                        // Includes
-    TRUE                                              // Ignore Duplicates
-  );
+  tripal_add_job('Cron: Checking expired collections', 'tripal',
+    'tripal_expire_collections', $args, 1, 1, $includes, TRUE);
+  
+  tripal_add_job('Cron: Checking expired files', 'tripal',
+    'tripal_expire_files', $args, 1, 1, $includes, TRUE);
+
 }
 
 /**
@@ -1310,44 +1520,6 @@ function tripal_html5_file_value($element, $input = FALSE, &$form_state) {
   }
 }
 
-
-/**
- * Implements hook_handle_uploaded_file().
- */
-function tripal_handle_uploaded_file($filename, $filepath, $type) {
-
-  global $user;
-
-  // Split the type into a node ID and form_key
-  list($id, $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();
-
-  // Create a file object.
-  if (!$fid) {
-    $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);
-    $fid = $file->fid;
-  }
-
-  $file = file_load($fid);
-  file_usage_add($file, 'tripal', $form_key, $id);
-  return $fid;
-
-}
-
 /**
  * Implements hook_field_display_alter().
  *