PageRenderTime 26ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/core/klargefile.class.php

http://awarenet.googlecode.com/
PHP | 326 lines | 197 code | 44 blank | 85 comment | 31 complexity | c2d60e1c7cd0688ec7d2a72fa22a235a MD5 | raw file
Possible License(s): GPL-3.0
  1. <?
  2. //--------------------------------------------------------------------------------------------------
  3. //* object for transferring large files between peers
  4. //--------------------------------------------------------------------------------------------------
  5. //+ When transferring large files between peers there can be issues with timeouts or memory
  6. //+ exhaustion - this object helps by breaking a lerge file up into many smaller ones and
  7. //+ transferring them piece by piece (see P2P worker object, started by cron).
  8. //+
  9. //+ Metadata is stored in an XML file like the following:
  10. //+
  11. //+ <klargefile>
  12. //+ <path>data/videos/1/2/3/somewhere-over-the-rainbow.mp4</path>
  13. //+ <hash>[sha1 hash of entire file]</hash>
  14. //+ <size>34826104</size>
  15. //+ <complete>no</complete>
  16. //+ <parts>
  17. //+ <part>
  18. //+ <index>0</index>
  19. //+ <hash>[sha1 hash of first 512k]</hash>
  20. //+ <status>ok</status>
  21. //+ <size>524288</size>
  22. //+ <fileName>data/</fileName>
  23. //+ </part>
  24. //+ <part>
  25. //+ <index>1</index>
  26. //+ <hash>[sha1 hash of second 512k]</hash>
  27. //+ <status>pending</status>
  28. //+ <size>524288</size>
  29. //+ <fileName>524288</fileName>
  30. //+ </part>
  31. //+ ... more parts here ....
  32. //+ </parts>
  33. //+ </klargefile>
  34. //+
  35. //+ The metadata filename will be ./data/transfer/meta/filename-as-alias.xml.php
  36. class KLargeFile {
  37. //----------------------------------------------------------------------------------------------
  38. // properties
  39. //----------------------------------------------------------------------------------------------
  40. var $parts; //% array of file part metadata [array:dict]
  41. var $count = 0; //% number of parts in file [int]
  42. var $loaded = false; //% set to true if metadata loaded [bool]
  43. var $path = ''; //% location of the file to be transferred / recieved [string]
  44. var $metaFile = ''; //% relative to installPath [string]
  45. var $hash = ''; //% sha1 hash of complete file [int]
  46. var $size = 0; //% total size of the file, bytes [int]
  47. var $partSize = 512; //% in kilobytes [int]
  48. var $complete = 'no'; //% set to true when all parts have been transferred [string]
  49. //----------------------------------------------------------------------------------------------
  50. //. constructor
  51. //----------------------------------------------------------------------------------------------
  52. function KLargeFile($path = '') {
  53. $this->parts = array();
  54. $this->path = $path;
  55. if ('' != $this->path) {
  56. $this->metaFile = $this->makeMetaFileName($path);
  57. $this->loadMetaXml();
  58. }
  59. }
  60. //----------------------------------------------------------------------------------------------
  61. //. load metadata XML file
  62. //----------------------------------------------------------------------------------------------
  63. //; note that metaFile should be set before calling this
  64. //opt: xml - raw XML used instead of metaFile if given [string]
  65. //returns: true on success, false on failure [bool]
  66. function loadMetaXml($xml = '') {
  67. global $kapenta;
  68. if (false == $kapenta->fileExists($this->metaFile)) { return false; }
  69. $this->parts = array();
  70. $isFile = false;
  71. if ('' == $xml) { // xml not given, load from disk
  72. $xml = $this->metaFile;
  73. $isFile = true;
  74. }
  75. $xd = new KXmlDocument($xml, $isFile);
  76. $children = $xd->getChildren(1); //% children of root node [array]
  77. foreach($children as $childId) {
  78. $child = $xd->getEntity($childId);
  79. switch(strtolower($child['type'])) {
  80. case 'path': $this->path = $child['value']; break;
  81. case 'hash': $this->hash = $child['value']; break;
  82. case 'size': $this->size = $child['value']; break;
  83. case 'complete': $this->complete = $child['value']; break;
  84. case 'parts':
  85. $parts = $xd->getChildren($childId);
  86. foreach($parts as $partId) { $this->parts[] = $xd->getChildren2d($partId); }
  87. break; //......................................................................
  88. }
  89. }
  90. $this->loaded = true;
  91. return true;
  92. }
  93. //----------------------------------------------------------------------------------------------
  94. //. save metadata XML file
  95. //----------------------------------------------------------------------------------------------
  96. //returns: true on success, false on failure [bool]
  97. function saveMetaXml() {
  98. global $kapenta;
  99. if (false == $this->loaded) { return false; }
  100. $xml = $this->toXml();
  101. $check = $kapenta->filePutContents($this->metaFile, $xml);
  102. return $check;
  103. }
  104. //----------------------------------------------------------------------------------------------
  105. //. make from an extant file
  106. //----------------------------------------------------------------------------------------------
  107. //; note that $this->path should be set before this is called
  108. //returns: true on success, false on failure [bool]
  109. function makeFromFile() {
  110. global $kapenta;
  111. if (false == $kapenta->fileExists($this->path)) { return false; }
  112. $absFile = $kapenta->installPath . $this->path;
  113. $this->hash = sha1_file($absFile);
  114. $this->size = filesize($absFile);
  115. $this->complete = 'yes';
  116. $this->parts = array();
  117. $numParts = ceil($this->size / ($this->partSize * 1024));
  118. for ($i = 0; $i < $numParts; $i++) {
  119. $raw = $this->getPart($i);
  120. $hash = sha1($raw);
  121. $fileName = 'data/transfer/parts/' . $kapenta->time() . '_' . $hash . '.part.php';
  122. $this->parts[$i] = array(
  123. 'index' => $i,
  124. 'status' => 'pending',
  125. 'hash' => $hash,
  126. 'size' => strlen($raw),
  127. 'fileName' => $fileName
  128. );
  129. }
  130. //TODO: make the parts array
  131. $this->loaded = true;
  132. return true;
  133. }
  134. //----------------------------------------------------------------------------------------------
  135. //. check if was have all parts
  136. //----------------------------------------------------------------------------------------------
  137. //returns: true if complete, false if not [bool]
  138. function checkCompletion() {
  139. $complete = 'yes';
  140. foreach($this->parts as $part) {
  141. if ('ok' != $part['status']) { $complete = 'no'; }
  142. }
  143. $this->complete = $complete;
  144. if ('yes' == $complete) { return true; }
  145. return false;
  146. }
  147. //----------------------------------------------------------------------------------------------
  148. //. rejoin parts into the original file
  149. //----------------------------------------------------------------------------------------------
  150. //returns: true on success, false on failure [bool]
  151. function stitchTogether() {
  152. global $kapenta;
  153. if ('no' == $this->complete) { return false; }
  154. //------------------------------------------------------------------------------------------
  155. // base64 decode and join all parts
  156. //------------------------------------------------------------------------------------------
  157. $kapenta->filePutContents($this->path, ''); // creates file and directories
  158. $fH = fopen($kapenta->installPath . $this->path, 'wb+'); // open for writing
  159. if (false == $fH) { return false; } // if cannot create
  160. foreach($this->parts as $part) {
  161. $part64 = $kapenta->fileGetContents($part['fileName']);
  162. $partBin = base64_decode($part64);
  163. fwrite($fH, $partBin);
  164. }
  165. fclose($fH);
  166. //------------------------------------------------------------------------------------------
  167. // make sure that it worked, delete if corrupt
  168. //------------------------------------------------------------------------------------------
  169. $newHash = sha1_file($kapenta->installPath . $this->path);
  170. if ($this->hash != $newHash) {
  171. unlink($kapenta->installPath . $this->path);
  172. return false;
  173. }
  174. return false;
  175. }
  176. //----------------------------------------------------------------------------------------------
  177. //. get a raw file segment
  178. //----------------------------------------------------------------------------------------------
  179. //returns: file part on success, empty string on failure [bool]
  180. function getPart($index) {
  181. global $kapenta;
  182. $raw = '';
  183. $fH = fopen($kapenta->installPath . $this->path, 'r'); //% read only file handle [int]
  184. if (false == $fH) { return $raw; }
  185. $skip = ($this->partSize * 1024) * $index; //% position to skip to [int]
  186. $check = fseek($fH, $skip); // move to position
  187. if (-1 == $check) { fclose($fH); return $raw; }
  188. $raw = fread($fH, ($this->partSize * 1024)); // read the part into $raw
  189. fclose($fH);
  190. return $raw;
  191. }
  192. //----------------------------------------------------------------------------------------------
  193. //. save a file part to disk
  194. //----------------------------------------------------------------------------------------------
  195. //arg: index - part number [int]
  196. //arg: content64 - base64 encoded fiel part [string]
  197. //returns: true on success, false on failure [bool]
  198. function storePart($index, $content64, $hash) {
  199. global $kapenta;
  200. if (false == array_key_exists($index, $this->parts)) { echo "no such part<br/>"; return false; }
  201. if ($hash != $this->parts[$index]['hash']) { echo "hash mismatch<br/>"; return false; }
  202. $fileName = $this->parts[$index]['fileName'];
  203. $check = $kapenta->filePutContents($fileName, $content64, true, true);
  204. if (false == $check) { echo "file could not be saved<br/>"; return false; }
  205. $this->parts[$index]['status'] = 'ok';
  206. return true;
  207. }
  208. //----------------------------------------------------------------------------------------------
  209. //. convert file location to alias
  210. //----------------------------------------------------------------------------------------------
  211. //arg: path - location relative to installPath [string]
  212. //returns: filename based on path name [string]
  213. function makeMetaFileName($path) {
  214. $path = str_replace('/', '-fs-', $path);
  215. $path = str_replace('\\', '-bs-', $path);
  216. $path = 'data/transfer/meta/' . $path . '.xml.php';
  217. return $path;
  218. }
  219. //----------------------------------------------------------------------------------------------
  220. //. serialize metadata to xml
  221. //----------------------------------------------------------------------------------------------
  222. function toXml() {
  223. $parts = "\t<parts>\n";
  224. foreach($this->parts as $part) {
  225. $parts .= ''
  226. . "\t\t<part>\n"
  227. . "\t\t\t<index>" . $part['index'] . "</index>\n"
  228. . "\t\t\t<hash>" . $part['hash'] . "</hash>\n"
  229. . "\t\t\t<status>" . $part['status'] . "</status>\n"
  230. . "\t\t\t<size>" . $part['size'] . "</size>\n"
  231. . "\t\t\t<fileName>" . $part['fileName'] . "</fileName>\n"
  232. . "\t\t</part>\n";
  233. }
  234. $parts .= "\t</parts>\n";
  235. $xml = ''
  236. . "<klargefile>\n"
  237. . "\t<path>" . $this->path . "</path>\n"
  238. . "\t<size>" . $this->size . "</size>\n"
  239. . "\t<complete>" . $this->size . "</complete>\n"
  240. . $parts
  241. . "</klargefile>\n";
  242. return $xml;
  243. }
  244. //----------------------------------------------------------------------------------------------
  245. //. render metadata in HTML for viewing / debugging
  246. //----------------------------------------------------------------------------------------------
  247. function toHtml() {
  248. global $theme;
  249. $html = ''; //% return value [string]
  250. $table = array(); //% html table [array:array:string]
  251. $table[] = array('Index', 'Hash', 'Status', 'Size', 'File Name');
  252. foreach($this->parts as $idx => $p) {
  253. $table[] = array($p['index'], $p['hash'], $p['status'], $p['size'], $p['fileName']);
  254. }
  255. $loaded = 'yes';
  256. if (false == $this->loaded) { $loaded = 'no'; }
  257. $html .= ''
  258. . "<b>path:</b> " . $this->path . "<br/>\n"
  259. . "<b>count:</b> " . $this->count . "<br/>\n"
  260. . "<b>loaded:</b> " . $loaded . "<br/>\n"
  261. . "<b>metaFile:</b> " . $this->metaFile . "<br/>\n"
  262. . "<b>size:</b> " . $this->size . "<br/>\n"
  263. . "<b>partSize:</b> " . $this->partSize . " (kb)<br/>\n"
  264. . "<b>complete:</b> " . $this->complete . "<br/>\n"
  265. . "<br/><b>Parts:</b><br/>\n"
  266. . $theme->arrayToHtmlTable($table, true, true);
  267. return $html;
  268. }
  269. }
  270. ?>