Disable forced gzip by default
[opentracker.git] / sync_daemon.pl
blob9e4bdb9cae475fb8891a5b2aaafda8ba43fcacf3
1 #!/usr/bin/perl
3 # This software was written by Philipp Wuensche <cryx-otsync@h3q.com>
4 # It is considered beerware.
6 use strict;
8 #use Convert::Bencode_XS qw(:all);
9 use Convert::Bencode qw(:all);
10 use Data::Dumper;
11 use LWP::UserAgent;
12 use URI::Escape;
14 # enable verbose output
15 my $debug = 0;
17 # tracker from where we get our sync data
18 my @trackers = ('127.0.0.1:8989');
19 # tracker to upload merged data
20 my @client_tracker = ('127.0.0.1:8989');
22 # time to wait between syncs
23 my $sleeptime = '300';
25 # SSL cert and key
26 my $ssl_cert = 'cert.pem';
27 my $ssl_key = 'key.pem';
29 foreach(@trackers) {
30 print "Syncing from: $_\n";
32 foreach(@client_tracker) {
33 print "Syncing to: $_\n";
36 my $file = shift;
39 # global hash for storing the merged syncs
40 my %merged_syncs;
42 while(1) {
43 %merged_syncs;
44 my @bencoded_sync_data;
46 # fetch the sync from every tracker and put it into an array in bencoded form
47 foreach my $tracker (@trackers) {
48 my $bencoded_sync = fetch_sync($tracker);
49 # my $bencoded_sync = fetch_sync_from_file($file);
50 if($bencoded_sync ne 0 && $bencoded_sync =~ /^d4\:sync/) {
51 push(@bencoded_sync_data,$bencoded_sync);
55 # bdecode every sync and throw it into the merged-sync
56 foreach my $bencoded_sync (@bencoded_sync_data) {
57 print "Doing merge...\n";
58 merge_sync(bdecode($bencoded_sync));
59 my $num_torrents = keys(%merged_syncs);
61 print "number of torrents: $num_torrents\n";
64 # number of max. peers in one changeset
65 my $peer_limit = 500;
66 # max number of changesets per commit
67 my $max_changesets = 10;
68 my $hash_count = 0;
69 my $peer_count = 0;
70 my $changeset;
71 my @escaped_changesets;
73 # run until all hashes are put into changesets and commited to the trackers
74 while(keys(%merged_syncs) != 0) {
76 foreach my $hash (keys(%merged_syncs)) {
77 print "Starting new changeset\n" if($peer_count == 0 && $debug);
78 my $num_peers = keys(%{$merged_syncs{$hash}});
80 print "\t$peer_count peers for $hash_count hashes in changeset\n" if($debug);
82 my $pack_hash = pack('H*',$hash);
84 # as long as the peer_limit is not reached, add new hashes with peers to the changeset hash-table
85 if($peer_count < $peer_limit) {
86 print "\t\tAdd $num_peers peers for $hash changeset\n" if($debug);
87 $peer_count = $peer_count + $num_peers;
88 foreach my $peer_socket (keys(%{$merged_syncs{$hash}})) {
90 my $flags = $merged_syncs{$hash}{$peer_socket};
92 print "\t\t\tAdd $peer_socket $flags\n" if($debug);
94 my $pack_peer = packme($peer_socket,$flags);
96 $changeset->{'sync'}->{$pack_hash} = $changeset->{'sync'}->{$pack_hash}.$pack_peer;
98 $hash_count++;
99 # hash is stored in the changeset, delete it from the hash-table
100 delete $merged_syncs{$hash};
103 # the peer_limit is reached or we are out of torrents, so start preparing a changeset
104 if($peer_count >= $peer_limit || keys(%merged_syncs) == 0) {
106 print "Commit changeset for $hash_count hashes with $peer_count peers total\n" if($debug);
108 # bencode the changeset
109 my $enc_changeset = bencode($changeset);
111 # URL-escape the changeset and put into an array of changesets
112 my $foobar = uri_escape($enc_changeset);
113 push(@escaped_changesets,$foobar);
115 # the changeset is ready and stored, so delete it from the changeset hash-table
116 delete $changeset->{'sync'};
118 $hash_count = 0;
119 $peer_count = 0;
120 print "\n\n\n" if($debug);
123 # if enought changesets are prepared or we are out of torrents for more changesets,
124 # sync the changesets to the trackers
125 if($#escaped_changesets == $max_changesets || keys(%merged_syncs) == 0) {
126 print "\tSync...\n";
127 sync_to_tracker(\@escaped_changesets);
128 undef @escaped_changesets;
134 print "Sleeping for $sleeptime seconds\n";
135 sleep $sleeptime;
138 sub connect_tracker {
139 # connect a tracker via HTTPS, returns the body of the response
140 my $url = shift;
142 $ENV{HTTPS_DEBUG} = 0;
143 $ENV{HTTPS_CERT_FILE} = $ssl_cert;
144 $ENV{HTTPS_KEY_FILE} = $ssl_key;
146 my $ua = new LWP::UserAgent;
147 my $req = new HTTP::Request('GET', $url);
148 my $res = $ua->request($req);
150 my $content = $res->content;
152 if($res->is_success()) {
153 return $content;
154 } else {
155 print $res->code."|".$res->status_line."\n";
156 return 0;
160 sub sync_to_tracker {
161 # commit changesets to a tracker
162 my @changesets = @{(shift)};
164 # prepare the URI with URL-encoded changesets concatenated by a &
165 my $uri = 'sync?';
166 foreach my $set (@changesets) {
167 $uri .= 'changeset='.$set.'&';
169 my $uri_length = length($uri);
171 # commit the collection of changesets to the tracker via HTTPS
172 foreach my $tracker (@client_tracker) {
173 print "\t\tTracker: $tracker (URI: $uri_length)\n";
174 my $url = "https://$tracker/".$uri;
175 connect_tracker($url);
179 sub packme {
180 # pack data
181 # returns ipaddr, port and flags in packed format
182 my $peer_socket = shift;
183 my $flags = shift;
185 my($a,$b,$c,$d,$port) = split(/[\.,\:]/,$peer_socket);
186 my $pack_peer = pack('C4 n1 b16',$a,$b,$c,$d,$port,$flags);
188 return $pack_peer;
191 sub unpackme {
192 # unpack packed data
193 # returns ipaddr. in quad-form with port (a.b.c.d:port) and flags as bitfield
194 # data is packed as:
195 # - 4 byte ipaddr. (unsigned char value)
196 # - 2 byte port (unsigned short in "network" (big-endian) order)
197 # - 2 byte flags (bit string (ascending bit order inside each byte))
198 my $data = shift;
200 my($a, $b, $c, $d, $port, $flags) = unpack('C4 n1 b16',$data);
201 my $peer_socket = "$a\.$b\.$c\.$d\:$port";
203 return($peer_socket,$flags);
206 sub fetch_sync {
207 # fetch sync from a tracker
208 my $tracker = shift;
209 my $url = "https://$tracker/sync";
211 print "Fetching from $url\n";
213 my $body = connect_tracker($url);
215 if($body && $body =~ /^d4\:sync/) {
216 return $body;
217 } else {
218 return 0;
222 sub fetch_sync_from_file {
223 # fetch sync from a file
224 my $file = shift;
225 my $body;
226 print "Fetching from file $file\n";
227 open(FILE,"<$file");
228 while(<FILE>) {
229 $body .= $_;
231 close(FILE);
232 return $body;
235 sub merge_sync {
236 # This builds a hash table with the torrenthash as keys. The value is a hash table again with the peer-socket as keys
237 # and flags in the value
238 # Example:
239 # 60dd2beb4197f71677c0f5ba92b956f7d04651e5 =>
240 # 192.168.23.23:2323 => 0000000000000000
241 # 23.23.23.23:2342 => 0000000100000000
242 # b220b4d7136e84a88abc090db88bec8604a808f3 =>
243 # 42.23.42.23:55555 => 0000000000000000
245 my $hashref = shift;
247 my $nonuniq_hash_counter = 0;
248 my $nonuniq_peer_counter = 0;
249 my $hash_counter = 0;
250 my $peer_counter = 0;
252 foreach my $key (keys(%{$hashref->{'sync'}})) {
253 # start merge for every sha1-hash in the sync
254 my $hash = unpack('H*',$key);
256 $hash_counter++;
257 $nonuniq_hash_counter++ if exists $merged_syncs{$hash};
259 while(${$hashref->{'sync'}}{$key} ne "")
261 # split the value into 8-byte and unpack it for getting peer-socket and flags
262 my($peer_socket,$flags) = unpackme(substr(${$hashref->{'sync'}}{$key},0,8,''));
264 $peer_counter++;
265 $nonuniq_peer_counter++ if exists $merged_syncs{$hash}{$peer_socket};
267 # Create a hash table with sha1-hash as key and a hash table as value.
268 # The hash table in the value has the peer-socket as key and flags as value
269 # If the entry already exists, the flags are ORed together, if not it is ORed with 0
270 $merged_syncs{$hash}{$peer_socket} = $flags | $merged_syncs{$hash}{$peer_socket};
273 print "$hash_counter hashes $nonuniq_hash_counter non-uniq, $peer_counter peers $nonuniq_peer_counter non-uniq.\n";
277 sub test_decode {
278 my $hashref = shift;
280 print "CHANGESET DEBUG OUTPUT\n";
282 print Dumper $hashref;
283 foreach my $key (keys(%{$hashref->{'sync'}})) {
284 my $hash = unpack('H*',$key);
286 print "Changeset for $hash\n";
287 while(${$hashref->{'sync'}}{$key} ne "")
290 my($peer_socket,$flags) = unpackme(substr(${$hashref->{'sync'}}{$key},0,8,''));
291 print "\tSocket: $peer_socket Flags: $flags\n";