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