summaryrefslogtreecommitdiffstats
path: root/classes/tracker.class.php
blob: cb7f438352b642338db3bfdcad82c1272bfcb048 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
<?
// TODO: Turn this into a class with nice functions like update_user, delete_torrent, etc.
class Tracker {
	const STATS_MAIN = 0;
	const STATS_USER = 1;

	public static $Requests = array();

	/**
	 * Send a GET request over a socket directly to the tracker
	 * For example, Tracker::update_tracker('change_passkey', array('oldpasskey' => OLD_PASSKEY, 'newpasskey' => NEW_PASSKEY)) will send the request:
	 * GET /tracker_32_char_secret_code/update?action=change_passkey&oldpasskey=OLD_PASSKEY&newpasskey=NEW_PASSKEY HTTP/1.1
	 *
	 * @param string $Action The action to send
	 * @param array $Updates An associative array of key->value pairs to send to the tracker
	 * @param boolean $ToIRC Sends a message to the channel #tracker with the GET URL.
	 */
	public static function update_tracker($Action, $Updates, $ToIRC = false) {
		// Build request
		$Get = TRACKER_SECRET . "/update?action=$Action";
		foreach ($Updates as $Key => $Value) {
			$Get .= "&$Key=$Value";
		}

		$MaxAttempts = 3;
		$Err = false;
		if (self::send_request($Get, $MaxAttempts, $Err) === false) {
			send_irc("PRIVMSG #tracker :$MaxAttempts $Err $Get");
			if (G::$Cache->get_value('ocelot_error_reported') === false) {
				send_irc('PRIVMSG ' . ADMIN_CHAN . " :Failed to update ocelot: $Err : $Get");
				G::$Cache->cache_value('ocelot_error_reported', true, 3600);
			}
			return false;
		}
		return true;
	}

	/**
	 * Get global peer stats from the tracker
	 *
	 * @return array(0 => $Leeching, 1 => $Seeding) or false if request failed
	 */
	public static function global_peer_count() {
		$Stats = self::get_stats(self::STATS_MAIN);
		if (isset($Stats['leechers tracked']) && isset($Stats['seeders tracked'])) {
			$Leechers = $Stats['leechers tracked'];
			$Seeders = $Stats['seeders tracked'];
		} else {
			return false;
		}
		return array($Leechers, $Seeders);
	}

	/**
	 * Get peer stats for a user from the tracker
	 *
	 * @param string $TorrentPass The user's pass key
	 * @return array(0 => $Leeching, 1 => $Seeding) or false if the request failed
	 */
	public static function user_peer_count($TorrentPass) {
		$Stats = self::get_stats(self::STATS_USER, array('key' => $TorrentPass));
		if ($Stats === false) {
			return false;
		}
		if (isset($Stats['leeching']) && isset($Stats['seeding'])) {
			$Leeching = $Stats['leeching'];
			$Seeding = $Stats['seeding'];
		} else {
			// User doesn't exist, but don't tell anyone
			$Leeching = $Seeding = 0;
		}
		return array($Leeching, $Seeding);
	}

	/**
	 * Get whatever info the tracker has to report
	 *
	 * @return results from get_stats()
	 */
	public static function info() {
		return self::get_stats(self::STATS_MAIN);
	}

	/**
	 * Send a stats request to the tracker and process the results
	 *
	 * @param int $Type Stats type to get
	 * @param array $Params Parameters required by stats type
	 * @return array with stats in named keys or false if the request failed
	 */
	private static function get_stats($Type, $Params = false) {
		if (!defined('TRACKER_REPORTKEY')) {
			return false;
		}
		$Get = TRACKER_REPORTKEY . '/report?';
		if ($Type === self::STATS_MAIN) {
			$Get .= 'get=stats';
		} elseif ($Type === self::STATS_USER && !empty($Params['key'])) {
			$Get .= "get=user&key=$Params[key]";
		} else {
			return false;
		}
		$Response = self::send_request($Get);
		if ($Response === false) {
			return false;
		}
		$Stats = array();
		foreach (explode("\n", $Response) as $Stat) {
			list($Val, $Key) = explode(" ", $Stat, 2);
			$Stats[$Key] = $Val;
		}
		return $Stats;
	}

	/**
	 * Send a request to the tracker
	 *
	 * @param string $Path GET string to send to the tracker
	 * @param int $MaxAttempts Maximum number of failed attempts before giving up
	 * @param $Err Variable to use as storage for the error string if the request fails
	 * @return tracker response message or false if the request failed
	 */
	private static function send_request($Get, $MaxAttempts = 1, &$Err = false) {
		$Header = "GET /$Get HTTP/1.1\r\nConnection: Close\r\n\r\n";
		$Attempts = 0;
		$Sleep = 0;
		$Success = false;
		$StartTime = microtime(true);
		while (!$Success && $Attempts++ < $MaxAttempts) {
			if ($Sleep) {
				sleep($Sleep);
			}
			$Sleep = 6;

			// Send request
			$File = fsockopen(TRACKER_HOST, TRACKER_PORT, $ErrorNum, $ErrorString);
			if ($File) {
				if (fwrite($File, $Header) === false) {
					$Err = "Failed to fwrite()";
					$Sleep = 3;
					continue;
				}
			} else {
				$Err = "Failed to fsockopen() - $ErrorNum - $ErrorString";
				continue;
			}

			// Check for response.
			$Response = '';
			while (!feof($File)) {
				$Response .= fread($File, 1024);
			}
			$DataStart = strpos($Response, "\r\n\r\n") + 4;
			$DataEnd = strrpos($Response, "\n");
			if ($DataEnd > $DataStart) {
				$Data = substr($Response, $DataStart, $DataEnd - $DataStart);
			} else {
				$Data = "";
			}
			$Status = substr($Response, $DataEnd + 1);
			if ($Status == "success") {
				$Success = true;
			}
		}
		$Request = array(
			'path' => substr($Get, strpos($Get, '/')),
			'response' => ($Success ? $Data : $Response),
			'status' => ($Success ? 'ok' : 'failed'),
			'time' => 1000 * (microtime(true) - $StartTime)
		);
		self::$Requests[] = $Request;
		if ($Success) {
			return $Data;
		}
		return false;
	}
}
?>