tripal.upload.inc 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. <?php
  2. function tripal_file_upload($type, $filename, $action = NULL, $chunk = 0) {
  3. global $user;
  4. $module = array_key_exists('module', $_GET) ? $_GET['module'] : '';
  5. $file_size = array_key_exists('file_size', $_GET) ? $_GET['file_size'] : '';
  6. $chunk_size = array_key_exists('chunk_size', $_GET) ? $_GET['chunk_size'] : '';
  7. $user_dir = 'public://tripal/users/' . $user->uid;
  8. if (!file_prepare_directory($user_dir, FILE_CREATE_DIRECTORY)) {
  9. $message = 'Could not access the directory on the server for storing this file.';
  10. watchdog('tripal', $message, array(), WATCHDOG_ERROR);
  11. drupal_json_output(array(
  12. 'status' => 'failed',
  13. 'message' => $message,
  14. 'file_id' => '',
  15. ));
  16. return;
  17. }
  18. // Allow the module that will own the file to make some checks. The module
  19. // is allowed to stop the upload as needed.
  20. $hook_name = $module . '_file_upload_check';
  21. if (function_exists($hook_name)) {
  22. $details = array(
  23. 'filename' => $filename,
  24. 'file_size' => $file_size,
  25. 'chunk_size' => $chunk_size,
  26. );
  27. $message = '';
  28. $status = $hook_name($action, $details, $message);
  29. if ($status === FALSE) {
  30. drupal_json_output(array(
  31. 'status' => 'failed',
  32. 'message' => $message,
  33. 'file_id' => '',
  34. ));
  35. return;
  36. }
  37. }
  38. switch ($action) {
  39. // If the action is 'put' then the callee is sending a chunk of the file
  40. case 'save':
  41. tripal_file_upload_put($filename, $chunk, $user_dir);
  42. break;
  43. case 'check':
  44. tripal_file_upload_verify($filename, $chunk, $user_dir);
  45. break;
  46. case 'merge':
  47. tripal_file_upload_merge($filename, $type, $user_dir);
  48. break;
  49. }
  50. }
  51. /**
  52. * Merges all chunks into a single file
  53. * @param unknown $filename
  54. */
  55. function tripal_file_upload_merge($filename, $type, $user_dir) {
  56. global $user;
  57. $module = $_GET['module'];
  58. $status = 'merging';
  59. $message = '';
  60. // Build the paths to the temp directory and merged file.
  61. $temp_dir = $user_dir . '/temp' . '/' . $filename;
  62. $merge_file = $user_dir . '/' . $filename;
  63. // If the temp directory where the chunks are found does not exist and the
  64. // client is requesting merge then most likely the file has already been
  65. // merged and the user hit the upload button again.
  66. if (file_exists($temp_dir)) {
  67. // Get the upload log.
  68. $log = tripal_file_upload_read_log($temp_dir);
  69. // Keep up with the expected file size.
  70. $merge_size = 0;
  71. // Open the new merged file.
  72. $merge_fh = fopen($merge_file, "w");
  73. if ($merge_fh){
  74. if (flock($merge_fh, LOCK_EX)) {
  75. $chunks_written = $log['chunks_written'];
  76. $max_chunk = max(array_keys($chunks_written));
  77. // Iterate through the chunks in order and see if any are missing.
  78. // If so then break out of the loop and fail. Otherwise concatentate
  79. // them together.
  80. for ($i = 0; $i <= $max_chunk; $i++) {
  81. if (!array_key_exists($i, $chunks_written)) {
  82. $status = 'failed';
  83. $message = 'Missing some chunks. Cannot complete file merge.';
  84. break;
  85. }
  86. $merge_size += $chunks_written[$i];
  87. $chunk_file = $temp_dir . '/' . $filename . '.chunk.' . $i;
  88. $cfh = fopen($chunk_file, 'r');
  89. while ($data = fread($cfh, 1024)) {
  90. fwrite($merge_fh, $data);
  91. }
  92. fclose($cfh);
  93. } // end for ($i = 0; $i <= $max_chunk; $i++) { ...
  94. if (filesize($merge_file) != $merge_size) {
  95. $status = 'failed';
  96. $message = 'File was uploaded but final merged size is incorrect: ' . $merge_file . '.';
  97. }
  98. }
  99. else {
  100. $status = 'failed';
  101. $message = 'Cannot lock merged file for writing: ' . $merge_file . '.';
  102. }
  103. }
  104. else {
  105. $status = 'failed';
  106. $message = 'Cannot open merged file: ' . $merge_file . '.';
  107. }
  108. flock($merge_fh, LOCK_UN);
  109. fclose($merge_fh);
  110. }
  111. // Make sure the merged file exists
  112. if (!file_exists($merge_file)) {
  113. $status = 'failed';
  114. $message = 'Merge file is missing after upload ' . $merge_file . '.';
  115. }
  116. $file_id = NULL;
  117. // If the file has been successfully merged then let the calling module
  118. // deal with it.
  119. if ($status != 'failed') {
  120. $function = $module . '_handle_uploaded_file';
  121. if(function_exists($function)) {
  122. $file_id = $function($filename, $merge_file, $type);
  123. if ($file_id) {
  124. $file = file_load($file_id);
  125. $status = 'completed';
  126. $full_path = drupal_realpath($file->uri);
  127. $md5sum = md5_file($full_path);
  128. $md5sum_file = fopen("$full_path.md5", "w");
  129. fwrite($md5sum_file, $md5sum);
  130. fclose($md5sum_file);
  131. unlink($temp_dir);
  132. }
  133. else {
  134. $status = 'failed';
  135. $message = 'Could not add file to the database.';
  136. }
  137. }
  138. else {
  139. $status = 'failed';
  140. $message = 'Cannot find the function: ' . $function . '().';
  141. }
  142. }
  143. if ($status == 'failed') {
  144. watchdog('tripal', $message, array(), WATCHDOG_ERROR);
  145. }
  146. drupal_json_output(array(
  147. 'status' => $status,
  148. 'message' => $message,
  149. 'file_id' => $file_id,
  150. ));
  151. }
  152. /**
  153. * Checks the size of a chunk to see if is fully uploaded.
  154. *
  155. * @return
  156. * returns a JSON array with a status, message and the
  157. * current chunk.
  158. */
  159. function tripal_file_upload_verify($filename, $chunk, $user_dir) {
  160. $chunk_size = $_GET['chunk_size'];
  161. // Store the chunked file in a temp folder.
  162. $temp_dir = $user_dir . '/temp' . '/' . $filename;
  163. if (!file_exists($temp_dir)) {
  164. mkdir($temp_dir, 0700, TRUE);
  165. }
  166. // Get the upload log.
  167. $log = tripal_file_upload_read_log($temp_dir);
  168. $chunks_written = $log['chunks_written'];
  169. $max_chunk = 0;
  170. if ($chunks_written) {
  171. $max_chunk = max(array_keys($chunks_written));
  172. }
  173. // Iterate through the chunks in order and see if any are missing.
  174. // If so then break out of the loop and this is the chunk to start
  175. // on.
  176. for ($i = 0; $i <= $max_chunk; $i++) {
  177. if (!array_key_exists($i, $chunks_written)) {
  178. break;
  179. }
  180. }
  181. drupal_json_output(array(
  182. 'status' => 'success',
  183. 'message' => '',
  184. 'curr_chunk' => $i,
  185. ));
  186. }
  187. /**
  188. * Saves the contents of the file being sent to the server.
  189. *
  190. * The file is saved using the filename the chunk number as an
  191. * extension. This function uses file locking to prevent two
  192. * jobs from writing to the same file at the same time.
  193. */
  194. function tripal_file_upload_put($filename, $chunk, $user_dir) {
  195. // Get the HTTP PUT data.
  196. $putdata = fopen("php://input", "r");
  197. $size = $_SERVER['CONTENT_LENGTH'];
  198. // Store the chunked file in a temp folder.
  199. $temp_dir = $user_dir . '/temp/' . $filename;
  200. if (!file_exists($temp_dir)) {
  201. mkdir($temp_dir, 0700, TRUE);
  202. }
  203. // Open the file for writing if doesn't already exist with the proper size.
  204. $chunk_file = $temp_dir . '/' . $filename . '.chunk.' . $chunk;
  205. if (!file_exists($chunk_file) or filesize($chunk_file) != $size) {
  206. // Read the data 1 KB at a time and write to the file
  207. $fh = fopen($chunk_file, "w");
  208. // Lock the file for writing. We don't want two different
  209. // processes trying to write to the same file at the same time.
  210. if (flock($fh, LOCK_EX)) {
  211. while ($data = fread($putdata, 1024)) {
  212. fwrite($fh, $data);
  213. }
  214. flock($fh, LOCK_UN);
  215. fclose($fh);
  216. }
  217. }
  218. fclose($putdata);
  219. // Get the current log, updated and re-write it.
  220. $log = tripal_file_upload_read_log($temp_dir);
  221. $log['chunks_written'][$chunk] = $size;
  222. tripal_file_upoad_write_log($temp_dir, $log);
  223. }
  224. /**
  225. * Reads the upload log file.
  226. *
  227. * The log file is used to keep track of which chunks have been uploaded.
  228. * The format is an array with a key of 'chunks_written' which each element
  229. * a key/value pair containing the chunk index as the key and the chunk size
  230. * as the value.
  231. *
  232. * @param $temp_dir
  233. * The directory where the log file will be written. It must be a unique
  234. * directory where only chunks from a single file are kept.
  235. */
  236. function tripal_file_upload_read_log($temp_dir) {
  237. $log_file = $temp_dir . '/tripal_upload.log';
  238. $log = NULL;
  239. if (file_exists($log_file)) {
  240. $fh = fopen($log_file, "r");
  241. if ($fh and flock($fh, LOCK_EX)) {
  242. $contents = '';
  243. while ($data = fread($fh, 1024)) {
  244. $contents .= $data;
  245. }
  246. $log = unserialize($contents);
  247. }
  248. flock($fh, LOCK_UN);
  249. fclose($fh);
  250. }
  251. if (!is_array($log)) {
  252. $log = array(
  253. 'chunks_written' => array(),
  254. );
  255. }
  256. return $log;
  257. }
  258. /**
  259. * Writes the upload log file.
  260. *
  261. * The log file is used to keep track of which chunks have been uploaded.
  262. * The format is an array with a key of 'chunks_written' which each element
  263. * a key/value pair containing the chunk index as the key and the chunk size
  264. * as the value.
  265. *
  266. * @param $temp_dir
  267. * The directory where the log file will be written. It must be a unique
  268. * directory where only chunks from a single file are kept.
  269. * @param $log
  270. * The log array, that is serialized and then written to the file.
  271. */
  272. function tripal_file_upoad_write_log($temp_dir, $log) {
  273. $log_file = $temp_dir . '/tripal_upload.log';
  274. if (!$log or !is_array($log)) {
  275. $log = array(
  276. 'chunks_written' => array(),
  277. );
  278. }
  279. // Get the last chunk read
  280. $fh = fopen($log_file, "w");
  281. if ($fh and flock($fh, LOCK_EX)) {
  282. fwrite($fh, serialize($log));
  283. }
  284. flock($fh, LOCK_UN);
  285. fclose($fh);
  286. }