filechunking.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <?php
  2. /**
  3. * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl>
  4. * This file is licensed under the Affero General Public License version 3 or
  5. * later.
  6. * See the COPYING-README file.
  7. */
  8. class OC_FileChunking {
  9. protected $info;
  10. protected $cache;
  11. static public function decodeName($name) {
  12. preg_match('/(?P<name>.*)-chunking-(?P<transferid>\d+)-(?P<chunkcount>\d+)-(?P<index>\d+)/', $name, $matches);
  13. return $matches;
  14. }
  15. /**
  16. * @param string[] $info
  17. */
  18. public function __construct($info) {
  19. $this->info = $info;
  20. }
  21. public function getPrefix() {
  22. $name = $this->info['name'];
  23. $transferid = $this->info['transferid'];
  24. return $name.'-chunking-'.$transferid.'-';
  25. }
  26. protected function getCache() {
  27. if (!isset($this->cache)) {
  28. $this->cache = new \OC\Cache\File();
  29. }
  30. return $this->cache;
  31. }
  32. /**
  33. * Stores the given $data under the given $key - the number of stored bytes is returned
  34. *
  35. * @param string $index
  36. * @param resource $data
  37. * @return int
  38. */
  39. public function store($index, $data) {
  40. $cache = $this->getCache();
  41. $name = $this->getPrefix().$index;
  42. $cache->set($name, $data);
  43. return $cache->size($name);
  44. }
  45. public function isComplete() {
  46. $prefix = $this->getPrefix();
  47. $parts = 0;
  48. $cache = $this->getCache();
  49. for($i=0; $i < $this->info['chunkcount']; $i++) {
  50. if ($cache->hasKey($prefix.$i)) {
  51. $parts ++;
  52. }
  53. }
  54. return $parts == $this->info['chunkcount'];
  55. }
  56. /**
  57. * Assembles the chunks into the file specified by the path.
  58. * Chunks are deleted afterwards.
  59. *
  60. * @param string $f target path
  61. *
  62. * @return integer assembled file size
  63. *
  64. * @throws \OC\InsufficientStorageException when file could not be fully
  65. * assembled due to lack of free space
  66. */
  67. public function assemble($f) {
  68. $cache = $this->getCache();
  69. $prefix = $this->getPrefix();
  70. $count = 0;
  71. for ($i = 0; $i < $this->info['chunkcount']; $i++) {
  72. $chunk = $cache->get($prefix.$i);
  73. // remove after reading to directly save space
  74. $cache->remove($prefix.$i);
  75. $count += fwrite($f, $chunk);
  76. }
  77. return $count;
  78. }
  79. /**
  80. * Returns the size of the chunks already present
  81. * @return integer size in bytes
  82. */
  83. public function getCurrentSize() {
  84. $cache = $this->getCache();
  85. $prefix = $this->getPrefix();
  86. $total = 0;
  87. for ($i = 0; $i < $this->info['chunkcount']; $i++) {
  88. $total += $cache->size($prefix.$i);
  89. }
  90. return $total;
  91. }
  92. /**
  93. * Removes all chunks which belong to this transmission
  94. */
  95. public function cleanup() {
  96. $cache = $this->getCache();
  97. $prefix = $this->getPrefix();
  98. for($i=0; $i < $this->info['chunkcount']; $i++) {
  99. $cache->remove($prefix.$i);
  100. }
  101. }
  102. /**
  103. * Removes one specific chunk
  104. * @param string $index
  105. */
  106. public function remove($index) {
  107. $cache = $this->getCache();
  108. $prefix = $this->getPrefix();
  109. $cache->remove($prefix.$index);
  110. }
  111. public function signature_split($orgfile, $input) {
  112. $info = unpack('n', fread($input, 2));
  113. $blocksize = $info[1];
  114. $this->info['transferid'] = mt_rand();
  115. $count = 0;
  116. $needed = array();
  117. $cache = $this->getCache();
  118. $prefix = $this->getPrefix();
  119. while (!feof($orgfile)) {
  120. $new_md5 = fread($input, 16);
  121. if (feof($input)) {
  122. break;
  123. }
  124. $data = fread($orgfile, $blocksize);
  125. $org_md5 = md5($data, true);
  126. if ($org_md5 == $new_md5) {
  127. $cache->set($prefix.$count, $data);
  128. } else {
  129. $needed[] = $count;
  130. }
  131. $count++;
  132. }
  133. return array(
  134. 'transferid' => $this->info['transferid'],
  135. 'needed' => $needed,
  136. 'count' => $count,
  137. );
  138. }
  139. /**
  140. * Assembles the chunks into the file specified by the path.
  141. * Also triggers the relevant hooks and proxies.
  142. *
  143. * @param string $path target path
  144. *
  145. * @return boolean assembled file size or false if file could not be created
  146. *
  147. * @throws \OC\InsufficientStorageException when file could not be fully
  148. * assembled due to lack of free space
  149. */
  150. public function file_assemble($path) {
  151. $absolutePath = \OC\Files\Filesystem::normalizePath(\OC\Files\Filesystem::getView()->getAbsolutePath($path));
  152. $data = '';
  153. // use file_put_contents as method because that best matches what this function does
  154. if (OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data)
  155. && \OC\Files\Filesystem::isValidPath($path)) {
  156. $path = \OC\Files\Filesystem::getView()->getRelativePath($absolutePath);
  157. $exists = \OC\Files\Filesystem::file_exists($path);
  158. $run = true;
  159. if(!$exists) {
  160. OC_Hook::emit(
  161. \OC\Files\Filesystem::CLASSNAME,
  162. \OC\Files\Filesystem::signal_create,
  163. array(
  164. \OC\Files\Filesystem::signal_param_path => $path,
  165. \OC\Files\Filesystem::signal_param_run => &$run
  166. )
  167. );
  168. }
  169. OC_Hook::emit(
  170. \OC\Files\Filesystem::CLASSNAME,
  171. \OC\Files\Filesystem::signal_write,
  172. array(
  173. \OC\Files\Filesystem::signal_param_path => $path,
  174. \OC\Files\Filesystem::signal_param_run => &$run
  175. )
  176. );
  177. if(!$run) {
  178. return false;
  179. }
  180. $target = \OC\Files\Filesystem::fopen($path, 'w');
  181. if($target) {
  182. $count = $this->assemble($target);
  183. fclose($target);
  184. if(!$exists) {
  185. OC_Hook::emit(
  186. \OC\Files\Filesystem::CLASSNAME,
  187. \OC\Files\Filesystem::signal_post_create,
  188. array( \OC\Files\Filesystem::signal_param_path => $path)
  189. );
  190. }
  191. OC_Hook::emit(
  192. \OC\Files\Filesystem::CLASSNAME,
  193. \OC\Files\Filesystem::signal_post_write,
  194. array( \OC\Files\Filesystem::signal_param_path => $path)
  195. );
  196. OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count);
  197. return $count > 0;
  198. }else{
  199. return false;
  200. }
  201. }
  202. return false;
  203. }
  204. }