You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

filechunking.php 6.3KB

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