filechunking.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Bart Visscher <bartv@thisnet.nl>
  6. * @author Felix Moeller <mail@felixmoeller.de>
  7. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Robin Appelman <robin@icewind.nl>
  10. * @author Roeland Jago Douma <roeland@famdouma.nl>
  11. * @author Thomas Müller <thomas.mueller@tmit.eu>
  12. * @author Thomas Tanghus <thomas@tanghus.net>
  13. * @author Vincent Petry <pvince81@owncloud.com>
  14. *
  15. * @license AGPL-3.0
  16. *
  17. * This code is free software: you can redistribute it and/or modify
  18. * it under the terms of the GNU Affero General Public License, version 3,
  19. * as published by the Free Software Foundation.
  20. *
  21. * This program is distributed in the hope that it will be useful,
  22. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. * GNU Affero General Public License for more details.
  25. *
  26. * You should have received a copy of the GNU Affero General Public License, version 3,
  27. * along with this program. If not, see <http://www.gnu.org/licenses/>
  28. *
  29. */
  30. class OC_FileChunking {
  31. protected $info;
  32. protected $cache;
  33. static public function decodeName($name) {
  34. preg_match('/(?P<name>.*)-chunking-(?P<transferid>\d+)-(?P<chunkcount>\d+)-(?P<index>\d+)/', $name, $matches);
  35. return $matches;
  36. }
  37. /**
  38. * @param string[] $info
  39. */
  40. public function __construct($info) {
  41. $this->info = $info;
  42. }
  43. public function getPrefix() {
  44. $name = $this->info['name'];
  45. $transferid = $this->info['transferid'];
  46. return $name.'-chunking-'.$transferid.'-';
  47. }
  48. protected function getCache() {
  49. if (!isset($this->cache)) {
  50. $this->cache = new \OC\Cache\File();
  51. }
  52. return $this->cache;
  53. }
  54. /**
  55. * Stores the given $data under the given $key - the number of stored bytes is returned
  56. *
  57. * @param string $index
  58. * @param resource $data
  59. * @return int
  60. */
  61. public function store($index, $data) {
  62. $cache = $this->getCache();
  63. $name = $this->getPrefix().$index;
  64. $cache->set($name, $data);
  65. return $cache->size($name);
  66. }
  67. public function isComplete() {
  68. $prefix = $this->getPrefix();
  69. $cache = $this->getCache();
  70. $chunkcount = (int)$this->info['chunkcount'];
  71. for($i=($chunkcount-1); $i >= 0; $i--) {
  72. if (!$cache->hasKey($prefix.$i)) {
  73. return false;
  74. }
  75. }
  76. return true;
  77. }
  78. /**
  79. * Assembles the chunks into the file specified by the path.
  80. * Chunks are deleted afterwards.
  81. *
  82. * @param resource $f target path
  83. *
  84. * @return integer assembled file size
  85. *
  86. * @throws \OC\InsufficientStorageException when file could not be fully
  87. * assembled due to lack of free space
  88. */
  89. public function assemble($f) {
  90. $cache = $this->getCache();
  91. $prefix = $this->getPrefix();
  92. $count = 0;
  93. for ($i = 0; $i < $this->info['chunkcount']; $i++) {
  94. $chunk = $cache->get($prefix.$i);
  95. // remove after reading to directly save space
  96. $cache->remove($prefix.$i);
  97. $count += fwrite($f, $chunk);
  98. // let php release the memory to work around memory exhausted error with php 5.6
  99. $chunk = null;
  100. }
  101. return $count;
  102. }
  103. /**
  104. * Returns the size of the chunks already present
  105. * @return integer size in bytes
  106. */
  107. public function getCurrentSize() {
  108. $cache = $this->getCache();
  109. $prefix = $this->getPrefix();
  110. $total = 0;
  111. for ($i = 0; $i < $this->info['chunkcount']; $i++) {
  112. $total += $cache->size($prefix.$i);
  113. }
  114. return $total;
  115. }
  116. /**
  117. * Removes all chunks which belong to this transmission
  118. */
  119. public function cleanup() {
  120. $cache = $this->getCache();
  121. $prefix = $this->getPrefix();
  122. for($i=0; $i < $this->info['chunkcount']; $i++) {
  123. $cache->remove($prefix.$i);
  124. }
  125. }
  126. /**
  127. * Removes one specific chunk
  128. * @param string $index
  129. */
  130. public function remove($index) {
  131. $cache = $this->getCache();
  132. $prefix = $this->getPrefix();
  133. $cache->remove($prefix.$index);
  134. }
  135. public function signature_split($orgfile, $input) {
  136. $info = unpack('n', fread($input, 2));
  137. $blocksize = $info[1];
  138. $this->info['transferid'] = mt_rand();
  139. $count = 0;
  140. $needed = array();
  141. $cache = $this->getCache();
  142. $prefix = $this->getPrefix();
  143. while (!feof($orgfile)) {
  144. $new_md5 = fread($input, 16);
  145. if (feof($input)) {
  146. break;
  147. }
  148. $data = fread($orgfile, $blocksize);
  149. $org_md5 = md5($data, true);
  150. if ($org_md5 == $new_md5) {
  151. $cache->set($prefix.$count, $data);
  152. } else {
  153. $needed[] = $count;
  154. }
  155. $count++;
  156. }
  157. return array(
  158. 'transferid' => $this->info['transferid'],
  159. 'needed' => $needed,
  160. 'count' => $count,
  161. );
  162. }
  163. /**
  164. * Assembles the chunks into the file specified by the path.
  165. * Also triggers the relevant hooks and proxies.
  166. *
  167. * @param \OC\Files\Storage\Storage $storage
  168. * @param string $path target path relative to the storage
  169. * @param string $absolutePath
  170. * @return bool assembled file size or false if file could not be created
  171. *
  172. * @throws \OC\ServerNotAvailableException
  173. */
  174. public function file_assemble($storage, $path, $absolutePath) {
  175. $data = '';
  176. // use file_put_contents as method because that best matches what this function does
  177. if (\OC\Files\Filesystem::isValidPath($path)) {
  178. $exists = $storage->file_exists($path);
  179. $run = true;
  180. $hookPath = \OC\Files\Filesystem::getView()->getRelativePath($absolutePath);
  181. if(!$exists) {
  182. OC_Hook::emit(
  183. \OC\Files\Filesystem::CLASSNAME,
  184. \OC\Files\Filesystem::signal_create,
  185. array(
  186. \OC\Files\Filesystem::signal_param_path => $hookPath,
  187. \OC\Files\Filesystem::signal_param_run => &$run
  188. )
  189. );
  190. }
  191. OC_Hook::emit(
  192. \OC\Files\Filesystem::CLASSNAME,
  193. \OC\Files\Filesystem::signal_write,
  194. array(
  195. \OC\Files\Filesystem::signal_param_path => $hookPath,
  196. \OC\Files\Filesystem::signal_param_run => &$run
  197. )
  198. );
  199. if(!$run) {
  200. return false;
  201. }
  202. $target = $storage->fopen($path, 'w');
  203. if($target) {
  204. $count = $this->assemble($target);
  205. fclose($target);
  206. if(!$exists) {
  207. OC_Hook::emit(
  208. \OC\Files\Filesystem::CLASSNAME,
  209. \OC\Files\Filesystem::signal_post_create,
  210. array( \OC\Files\Filesystem::signal_param_path => $hookPath)
  211. );
  212. }
  213. OC_Hook::emit(
  214. \OC\Files\Filesystem::CLASSNAME,
  215. \OC\Files\Filesystem::signal_post_write,
  216. array( \OC\Files\Filesystem::signal_param_path => $hookPath)
  217. );
  218. return $count > 0;
  219. }else{
  220. return false;
  221. }
  222. }
  223. return false;
  224. }
  225. }