|
@@ -0,0 +1,164 @@
|
|
1
|
+<?php
|
|
2
|
+
|
|
3
|
+function safe_b64encode($string) {
|
|
4
|
+ $data = base64_encode($string);
|
|
5
|
+ $data = str_replace(array('+','/','='),array('-','_',''),$data);
|
|
6
|
+ return $data;
|
|
7
|
+}
|
|
8
|
+
|
|
9
|
+class Torrent {
|
|
10
|
+ public $metadata;
|
|
11
|
+ public $name;
|
|
12
|
+ public $id;
|
|
13
|
+
|
|
14
|
+ function __construct ($url_or_file) {
|
|
15
|
+ $this->name = urldecode(end(explode('/', $url_or_file)));
|
|
16
|
+ $this->id = safe_b64encode($url_or_file);
|
|
17
|
+
|
|
18
|
+ $filesize = (int) array_change_key_case(
|
|
19
|
+ get_headers($url_or_file, TRUE))['content-length'];
|
|
20
|
+ $piece_size = $this->get_piece_size($filesize);
|
|
21
|
+ $log = getcwd() . "/log/" . $this->id;
|
|
22
|
+
|
|
23
|
+ # now create the actual torrent
|
|
24
|
+ $this->metadata = array(
|
|
25
|
+ 'announce' => 'http://academictorrents.com/announce.php',
|
|
26
|
+ 'encoding' => 'UTF-8',
|
|
27
|
+ 'info' => array(
|
|
28
|
+ 'length' => $filesize,
|
|
29
|
+ 'name' => $this->name,
|
|
30
|
+ 'pieces' => $this->make_pieces($url_or_file, $piece_size,
|
|
31
|
+ $filesize, $log),
|
|
32
|
+ 'piece length' => $piece_size,
|
|
33
|
+ ),
|
|
34
|
+ 'url-list' => array($url_or_file),
|
|
35
|
+ );
|
|
36
|
+ $this->save();
|
|
37
|
+ }
|
|
38
|
+
|
|
39
|
+ static function bencode($var) {
|
|
40
|
+ if (is_int($var)) {
|
|
41
|
+ return 'i' . $var . 'e';
|
|
42
|
+ } else if (is_string($var)) {
|
|
43
|
+ return strlen($var) . ':' . $var;
|
|
44
|
+ } else if (is_array($var)) {
|
|
45
|
+ # must distinguish between dict and list
|
|
46
|
+ for ($i = 0; $i < count($var); $i++) {
|
|
47
|
+ # if dict, cannot index using ints?
|
|
48
|
+ if (!isset($var[$i])) {
|
|
49
|
+ $dictionary = $var;
|
|
50
|
+ ksort($dictionary);
|
|
51
|
+ $ret = 'd';
|
|
52
|
+ foreach ($dictionary as $key => $value) {
|
|
53
|
+ $ret .= Torrent::bencode($key) . Torrent::bencode($value);
|
|
54
|
+ }
|
|
55
|
+ return $ret . 'e';
|
|
56
|
+ }
|
|
57
|
+ }
|
|
58
|
+ $ret = 'l';
|
|
59
|
+ foreach ($var as $value) {
|
|
60
|
+ $ret .= Torrent::bencode($value);
|
|
61
|
+ }
|
|
62
|
+ return $ret . 'e';
|
|
63
|
+ }
|
|
64
|
+ }
|
|
65
|
+
|
|
66
|
+ private function get_piece_size($filesize) {
|
|
67
|
+ $piece_length = 524288;
|
|
68
|
+ while (ceil($filesize / $piece_length) == 1) {
|
|
69
|
+ $piece_length /= 2;
|
|
70
|
+ }
|
|
71
|
+ while (ceil($filesize / $piece_length) > 1500) {
|
|
72
|
+ $piece_length *= 2;
|
|
73
|
+ }
|
|
74
|
+ return $piece_length;
|
|
75
|
+ }
|
|
76
|
+
|
|
77
|
+ private function make_pieces($url, $piece_length, $filesize, $logfile) {
|
|
78
|
+ if (($fp = fopen($url, 'r')) == FALSE) {
|
|
79
|
+ return FALSE;
|
|
80
|
+ }
|
|
81
|
+ $pieces = '';
|
|
82
|
+ $part = '';
|
|
83
|
+
|
|
84
|
+ $position = 0;
|
|
85
|
+ $i = 0;
|
|
86
|
+ while ($position < $filesize) {
|
|
87
|
+ $bytes_read = 0;
|
|
88
|
+ # fread doesn't actually read in the correct number of bytes
|
|
89
|
+ # piece together multiple freads
|
|
90
|
+ while ($bytes_read < $piece_length && $position < $filesize) {
|
|
91
|
+ $this_part = fread($fp, min($piece_length, $filesize - $position));
|
|
92
|
+ $bytes_read += strlen($this_part);
|
|
93
|
+ $position += strlen($this_part);
|
|
94
|
+ $part .= $this_part;
|
|
95
|
+ }
|
|
96
|
+ $next_part = substr($part, $piece_length);
|
|
97
|
+ $part = substr($part, 0, $piece_length);
|
|
98
|
+ $piece = sha1($part, $raw_output=TRUE);
|
|
99
|
+ $pieces .= $piece;
|
|
100
|
+
|
|
101
|
+ $part = $next_part;
|
|
102
|
+
|
|
103
|
+ if ($position > $filesize) {
|
|
104
|
+ $position = $filesize;
|
|
105
|
+ }
|
|
106
|
+ # log progress every 5 pieces
|
|
107
|
+ if ($i++ % 5 == 0 || $position == $filesize) {
|
|
108
|
+ $log = fopen($logfile, "w");
|
|
109
|
+ fwrite($log, $position . '/' . $filesize);
|
|
110
|
+ fflush($f);
|
|
111
|
+ fclose($log);
|
|
112
|
+ }
|
|
113
|
+ }
|
|
114
|
+ fflush($f);
|
|
115
|
+ fclose($fp);
|
|
116
|
+ return $pieces;
|
|
117
|
+ }
|
|
118
|
+
|
|
119
|
+ private function save() {
|
|
120
|
+ $dir = getcwd() . "/torrents/" . $this->id;
|
|
121
|
+ mkdir($dir);
|
|
122
|
+ $f = fopen($dir . "/" . $this->name . ".torrent", "w");
|
|
123
|
+ fwrite($f, Torrent::bencode($this->metadata));
|
|
124
|
+ fflush($f);
|
|
125
|
+ fclose($f);
|
|
126
|
+ }
|
|
127
|
+}
|
|
128
|
+
|
|
129
|
+/*
|
|
130
|
+ create:
|
|
131
|
+ 1. Input: GET variables url-encoded string "url".
|
|
132
|
+ 2. Creates log for "id" in ./log/ for the download.
|
|
133
|
+ 3. Streamingly creates torrent, updating log.
|
|
134
|
+ 4. Dumps finished torrent file in ./torrents/
|
|
135
|
+*/
|
|
136
|
+
|
|
137
|
+# Main
|
|
138
|
+if (isset($_GET["url"]) && filter_var($_GET["url"], FILTER_VALIDATE_URL)) {
|
|
139
|
+ $logname = getcwd() . "/log/" . safe_b64encode($_GET["url"]);
|
|
140
|
+
|
|
141
|
+ # if log exists, don't start new torrent
|
|
142
|
+ if (file_exists($logname)) {
|
|
143
|
+ # check status; return "done", or "processing"
|
|
144
|
+ $contents = explode("/", stream_get_contents(fopen($logname, "r")));
|
|
145
|
+ $progress = intval($contents[0]);
|
|
146
|
+ $total = intval($contents[1]);
|
|
147
|
+ if ($progress==$total) {
|
|
148
|
+ echo(json_encode(array(
|
|
149
|
+ 'status' => 'OK',
|
|
150
|
+ 'msg' => "Torrent complete!")));
|
|
151
|
+ } else {
|
|
152
|
+ echo(json_encode(array(
|
|
153
|
+ 'status' => 'OK',
|
|
154
|
+ 'msg' => "Torrent already processing!")));
|
|
155
|
+ }
|
|
156
|
+ } else {
|
|
157
|
+ $torrent = new Torrent($_GET["url"]);
|
|
158
|
+ }
|
|
159
|
+} else {
|
|
160
|
+ echo(json_encode(array(
|
|
161
|
+ 'status' => 'ERROR',
|
|
162
|
+ 'msg' => "Requires GET variable 'url'")));
|
|
163
|
+};
|
|
164
|
+?>
|