1 /*
2  * Copyright (C) Azureus Software, Inc, All Rights Reserved.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details ( see the LICENSE file ).
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18 
19 package org.gudy.azureus2.ui.swt.views.clientstats;
20 
21 import java.net.InetAddress;
22 import java.text.SimpleDateFormat;
23 import java.util.*;
24 import java.util.List;
25 
26 import org.eclipse.swt.SWT;
27 import org.eclipse.swt.layout.*;
28 import org.eclipse.swt.widgets.*;
29 import org.gudy.azureus2.core3.download.DownloadManager;
30 import org.gudy.azureus2.core3.download.DownloadManagerPeerListener;
31 import org.gudy.azureus2.core3.download.DownloadManagerState;
32 import org.gudy.azureus2.core3.global.GlobalManagerListener;
33 import org.gudy.azureus2.core3.peer.PEPeer;
34 import org.gudy.azureus2.core3.peer.PEPeerListener;
35 import org.gudy.azureus2.core3.peer.PEPeerManager;
36 import org.gudy.azureus2.core3.peer.impl.PEPeerTransport;
37 import org.gudy.azureus2.core3.util.*;
38 import org.gudy.azureus2.plugins.ui.UIManager;
39 import org.gudy.azureus2.plugins.ui.tables.TableColumn;
40 import org.gudy.azureus2.plugins.ui.tables.TableColumnCreationListener;
41 import org.gudy.azureus2.plugins.ui.tables.TableManager;
42 import org.gudy.azureus2.pluginsimpl.local.PluginInitializer;
43 import org.gudy.azureus2.ui.swt.Utils;
44 import org.gudy.azureus2.ui.swt.mainwindow.ClipboardCopy;
45 import org.gudy.azureus2.ui.swt.views.table.TableViewSWT;
46 import org.gudy.azureus2.ui.swt.views.table.impl.TableViewFactory;
47 import org.gudy.azureus2.ui.swt.views.table.impl.TableViewTab;
48 
49 import com.aelitis.azureus.core.AzureusCore;
50 import com.aelitis.azureus.core.AzureusCoreFactory;
51 import com.aelitis.azureus.core.AzureusCoreRunningListener;
52 import com.aelitis.azureus.core.peermanager.peerdb.PeerItem;
53 import com.aelitis.azureus.core.peermanager.piecepicker.util.BitFlags;
54 import com.aelitis.azureus.core.util.bloom.BloomFilter;
55 import com.aelitis.azureus.core.util.bloom.BloomFilterFactory;
56 import com.aelitis.azureus.ui.common.table.TableColumnCore;
57 import com.aelitis.azureus.ui.common.table.TableLifeCycleListener;
58 import com.aelitis.azureus.ui.common.table.TableRowCore;
59 import com.aelitis.azureus.ui.common.table.impl.TableColumnManager;
60 import com.aelitis.azureus.util.MapUtils;
61 
62 public class ClientStatsView
63 	extends TableViewTab<ClientStatsDataSource>
64 	implements TableLifeCycleListener, GlobalManagerListener,
65 	DownloadManagerPeerListener
66 {
67 	private static final String CONFIG_FILE = "ClientStats.dat";
68 
69 	private static final String CONFIG_FILE_ARCHIVE = "ClientStats_%1.dat";
70 
71 	private static final int BLOOMFILTER_SIZE = 100000;
72 
73 	private static final int BLOOMFILTER_PEERID_SIZE = 50000;
74 
75 	private static final String TABLEID = "ClientStats";
76 
77 	private AzureusCore core;
78 
79 	private TableViewSWT<ClientStatsDataSource> tv;
80 
81 	private boolean columnsAdded;
82 
83 	private final Map<String, ClientStatsDataSource> mapData = new HashMap<String, ClientStatsDataSource>();
84 
85 	private Composite parent;
86 
87 	private BloomFilter bloomFilter;
88 
89 	private BloomFilter bloomFilterPeerId;
90 
91 	private ClientStatsOverall overall;
92 
93 	private long startedListeningOn;
94 
95 	private long totalTime;
96 
97 	private long lastAdd;
98 
99 	private GregorianCalendar calendar = new GregorianCalendar();
100 
101 	private int lastAddMonth;
102 
103 	private static boolean registered = false;
104 
ClientStatsView()105 	public ClientStatsView() {
106 		super("ClientStats");
107 
108 		initAndLoad();
109 
110 		AzureusCoreFactory.addCoreRunningListener(new AzureusCoreRunningListener() {
111 			public void azureusCoreRunning(AzureusCore core) {
112 				initColumns(core);
113 				register(core);
114 			}
115 		});
116 	}
117 
initComposite(Composite composite)118 	public Composite initComposite(Composite composite) {
119 		parent = new Composite(composite, SWT.BORDER);
120 		parent.setLayout(new FormLayout());
121 		Layout layout = composite.getLayout();
122 		if (layout instanceof GridLayout) {
123 			Utils.setLayoutData(parent, new GridData(SWT.FILL, SWT.FILL, true, true));
124 		} else if (layout instanceof FormLayout) {
125 			Utils.setLayoutData(parent, Utils.getFilledFormData());
126 		}
127 
128 		return parent;
129 	}
130 
tableViewTabInitComplete()131 	public void tableViewTabInitComplete() {
132 		Composite cTV = (Composite) parent.getChildren()[0];
133 		Composite cBottom = new Composite(parent, SWT.None);
134 		FormData fd;
135 		fd = Utils.getFilledFormData();
136 		fd.bottom = new FormAttachment(cBottom);
137 		Utils.setLayoutData(cTV, fd);
138 		fd = Utils.getFilledFormData();
139 		fd.top = null;
140 		Utils.setLayoutData(cBottom, fd);
141 		cBottom.setLayout(new FormLayout());
142 
143 		Button btnCopy = new Button(cBottom, SWT.PUSH);
144 		Utils.setLayoutData(btnCopy, new FormData());
145 		btnCopy.setText("Copy");
146 		btnCopy.addListener(SWT.Selection, new Listener() {
147 			public void handleEvent(Event event) {
148 				TableRowCore[] rows = tv.getRows();
149 				StringBuilder sb = new StringBuilder();
150 
151 				sb.append(new SimpleDateFormat("MMM yyyy").format(new Date()));
152 				sb.append("\n");
153 
154 				sb.append("Hits,Client,Bytes Sent,Bytes Received,Bad Bytes\n");
155 				for (TableRowCore row : rows) {
156 					ClientStatsDataSource stat = (ClientStatsDataSource) row.getDataSource();
157 					if (stat == null) {
158 						continue;
159 					}
160 					sb.append(stat.count);
161 					sb.append(",");
162 					sb.append(stat.client.replaceAll(",", ""));
163 					sb.append(",");
164 					sb.append(stat.bytesSent);
165 					sb.append(",");
166 					sb.append(stat.bytesReceived);
167 					sb.append(",");
168 					sb.append(stat.bytesDiscarded);
169 					sb.append("\n");
170 				}
171 				ClipboardCopy.copyToClipBoard(sb.toString());
172 			}
173 		});
174 
175 		Button btnCopyShort = new Button(cBottom, SWT.PUSH);
176 		Utils.setLayoutData(btnCopyShort, new FormData());
177 		btnCopyShort.setText("Copy > 1%");
178 		btnCopyShort.addListener(SWT.Selection, new Listener() {
179 			public void handleEvent(Event event) {
180 				StringBuilder sb = new StringBuilder();
181 
182 				sb.append(new SimpleDateFormat("MMM ''yy").format(new Date()));
183 				sb.append("] ");
184 				sb.append(overall.count);
185 				sb.append(": ");
186 
187 				ClientStatsDataSource[] stats;
188 
189 				synchronized (mapData) {
190 
191 					stats = mapData.values().toArray(new ClientStatsDataSource[0]);
192 				}
193 
194 				Arrays.sort(stats, new Comparator<ClientStatsDataSource>() {
195 					public int compare(ClientStatsDataSource o1, ClientStatsDataSource o2) {
196 						if (o1.count == o2.count) {
197 							return 0;
198 						}
199 						return o1.count > o2.count ? -1 : 1;
200 					}
201 				});
202 
203 				boolean first = true;
204 				for (ClientStatsDataSource stat : stats) {
205 					int pct = (int) (stat.count * 1000 / overall.count);
206 					if (pct < 10) {
207 						continue;
208 					}
209 					if (first) {
210 						first = false;
211 					} else {
212 						sb.append(", ");
213 					}
214 					sb.append(DisplayFormatters.formatPercentFromThousands(pct));
215 					sb.append(" ");
216 					sb.append(stat.client);
217 				}
218 
219 				Arrays.sort(stats, new Comparator<ClientStatsDataSource>() {
220 					public int compare(ClientStatsDataSource o1, ClientStatsDataSource o2) {
221 						float v1 = (float) o1.bytesReceived / o1.count;
222 						float v2 = (float) o2.bytesReceived / o2.count;
223 						if (v1 == v2) {
224 							return 0;
225 						}
226 						return v1 > v2 ? -1 : 1;
227 					}
228 				});
229 
230 				int top = 5;
231 				first = true;
232 				sb.append("\nBest Seeders (");
233 				long total = 0;
234 				for (ClientStatsDataSource stat : stats) {
235 					total += stat.bytesReceived;
236 				}
237 				sb.append(DisplayFormatters.formatByteCountToKiBEtc(total, false, true,
238 						0));
239 				sb.append(" Downloaded): ");
240 				for (ClientStatsDataSource stat : stats) {
241 					if (first) {
242 						first = false;
243 					} else {
244 						sb.append(", ");
245 					}
246 					sb.append(DisplayFormatters.formatByteCountToKiBEtc(
247 							stat.bytesReceived / stat.count, false, true, 0));
248 					sb.append(" per ");
249 					sb.append(stat.client);
250 					sb.append("(x");
251 					sb.append(stat.count);
252 					sb.append(")");
253 					if (--top <= 0) {
254 						break;
255 					}
256 				}
257 
258 				Arrays.sort(stats, new Comparator<ClientStatsDataSource>() {
259 					public int compare(ClientStatsDataSource o1, ClientStatsDataSource o2) {
260 						float v1 = (float) o1.bytesDiscarded / o1.count;
261 						float v2 = (float) o2.bytesDiscarded / o2.count;
262 						if (v1 == v2) {
263 							return 0;
264 						}
265 						return v1 > v2 ? -1 : 1;
266 					}
267 				});
268 				top = 5;
269 				first = true;
270 				sb.append("\nMost Discarded (");
271 				total = 0;
272 				for (ClientStatsDataSource stat : stats) {
273 					total += stat.bytesDiscarded;
274 				}
275 				sb.append(DisplayFormatters.formatByteCountToKiBEtc(total, false, true,
276 						0));
277 				sb.append(" Discarded): ");
278 				for (ClientStatsDataSource stat : stats) {
279 					if (first) {
280 						first = false;
281 					} else {
282 						sb.append(", ");
283 					}
284 					sb.append(DisplayFormatters.formatByteCountToKiBEtc(
285 							stat.bytesDiscarded / stat.count, false, true, 0));
286 					sb.append(" per ");
287 					sb.append(stat.client);
288 					sb.append("(x");
289 					sb.append(stat.count);
290 					sb.append(")");
291 					if (--top <= 0) {
292 						break;
293 					}
294 				}
295 
296 				Arrays.sort(stats, new Comparator<ClientStatsDataSource>() {
297 					public int compare(ClientStatsDataSource o1, ClientStatsDataSource o2) {
298 						float v1 = (float) o1.bytesSent / o1.count;
299 						float v2 = (float) o2.bytesSent / o2.count;
300 						if (v1 == v2) {
301 							return 0;
302 						}
303 						return v1 > v2 ? -1 : 1;
304 					}
305 				});
306 				top = 5;
307 				first = true;
308 				sb.append("\nMost Fed (");
309 				total = 0;
310 				for (ClientStatsDataSource stat : stats) {
311 					total += stat.bytesSent;
312 				}
313 				sb.append(DisplayFormatters.formatByteCountToKiBEtc(total, false, true,
314 						0));
315 				sb.append(" Sent): ");
316 				for (ClientStatsDataSource stat : stats) {
317 					if (first) {
318 						first = false;
319 					} else {
320 						sb.append(", ");
321 					}
322 					sb.append(DisplayFormatters.formatByteCountToKiBEtc(
323 							stat.bytesSent / stat.count, false, true, 0));
324 					sb.append(" per ");
325 					sb.append(stat.client);
326 					sb.append("(x");
327 					sb.append(stat.count);
328 					sb.append(")");
329 					if (--top <= 0) {
330 						break;
331 					}
332 				}
333 
334 				ClipboardCopy.copyToClipBoard(sb.toString());
335 			}
336 		});
337 		fd = new FormData();
338 		fd.left = new FormAttachment(btnCopy, 5);
339 		Utils.setLayoutData(btnCopyShort, fd);
340 	}
341 
initYourTableView()342 	public TableViewSWT<ClientStatsDataSource> initYourTableView() {
343 		tv = TableViewFactory.createTableViewSWT(ClientStatsDataSource.class,
344 				TABLEID, getPropertiesPrefix(), new TableColumnCore[0],
345 				ColumnCS_Count.COLUMN_ID, SWT.MULTI | SWT.FULL_SELECTION | SWT.VIRTUAL);
346 		/*
347 				tv.addTableDataSourceChangedListener(this, true);
348 				tv.addRefreshListener(this, true);
349 				tv.addSelectionListener(this, false);
350 				tv.addMenuFillListener(this);
351 				*/
352 		tv.addLifeCycleListener(this);
353 
354 		return tv;
355 	}
356 
initColumns(AzureusCore core)357 	private void initColumns(AzureusCore core) {
358 		synchronized (ClientStatsView.class) {
359 
360 			if (columnsAdded) {
361 
362 				return;
363 			}
364 
365 			columnsAdded = true;
366 		}
367 
368 		UIManager uiManager = PluginInitializer.getDefaultInterface().getUIManager();
369 
370 		TableManager tableManager = uiManager.getTableManager();
371 
372 		tableManager.registerColumn(ClientStatsDataSource.class,
373 				ColumnCS_Name.COLUMN_ID, new TableColumnCreationListener() {
374 					public void tableColumnCreated(TableColumn column) {
375 						new ColumnCS_Name(column);
376 					}
377 				});
378 		tableManager.registerColumn(ClientStatsDataSource.class,
379 				ColumnCS_Count.COLUMN_ID, new TableColumnCreationListener() {
380 					public void tableColumnCreated(TableColumn column) {
381 						new ColumnCS_Count(column);
382 					}
383 				});
384 		tableManager.registerColumn(ClientStatsDataSource.class,
385 				ColumnCS_Discarded.COLUMN_ID, new TableColumnCreationListener() {
386 					public void tableColumnCreated(TableColumn column) {
387 						new ColumnCS_Discarded(column);
388 					}
389 				});
390 		tableManager.registerColumn(ClientStatsDataSource.class,
391 				ColumnCS_Received.COLUMN_ID, new TableColumnCreationListener() {
392 					public void tableColumnCreated(TableColumn column) {
393 						new ColumnCS_Received(column);
394 					}
395 				});
396 		tableManager.registerColumn(ClientStatsDataSource.class,
397 				ColumnCS_ReceivedPer.COLUMN_ID, new TableColumnCreationListener() {
398 					public void tableColumnCreated(TableColumn column) {
399 						new ColumnCS_ReceivedPer(column);
400 					}
401 				});
402 		tableManager.registerColumn(ClientStatsDataSource.class,
403 				ColumnCS_Sent.COLUMN_ID, new TableColumnCreationListener() {
404 					public void tableColumnCreated(TableColumn column) {
405 						new ColumnCS_Sent(column);
406 					}
407 				});
408 		tableManager.registerColumn(ClientStatsDataSource.class,
409 				ColumnCS_Pct.COLUMN_ID, new TableColumnCreationListener() {
410 					public void tableColumnCreated(TableColumn column) {
411 						new ColumnCS_Pct(column);
412 					}
413 				});
414 
415 		for (final String network : AENetworkClassifier.AT_NETWORKS) {
416 			tableManager.registerColumn(ClientStatsDataSource.class, network + "."
417 					+ ColumnCS_Sent.COLUMN_ID, new TableColumnCreationListener() {
418 				public void tableColumnCreated(TableColumn column) {
419 					column.setUserData("network", network);
420 					new ColumnCS_Sent(column);
421 				}
422 			});
423 			tableManager.registerColumn(ClientStatsDataSource.class, network + "."
424 					+ ColumnCS_Discarded.COLUMN_ID, new TableColumnCreationListener() {
425 				public void tableColumnCreated(TableColumn column) {
426 					column.setUserData("network", network);
427 					new ColumnCS_Discarded(column);
428 				}
429 			});
430 			tableManager.registerColumn(ClientStatsDataSource.class, network + "."
431 					+ ColumnCS_Received.COLUMN_ID, new TableColumnCreationListener() {
432 				public void tableColumnCreated(TableColumn column) {
433 					column.setUserData("network", network);
434 					new ColumnCS_Received(column);
435 				}
436 			});
437 			tableManager.registerColumn(ClientStatsDataSource.class, network + "."
438 					+ ColumnCS_Count.COLUMN_ID, new TableColumnCreationListener() {
439 				public void tableColumnCreated(TableColumn column) {
440 					column.setUserData("network", network);
441 					new ColumnCS_Count(column);
442 				}
443 			});
444 		}
445 
446 		TableColumnManager tcManager = TableColumnManager.getInstance();
447 		tcManager.setDefaultColumnNames(TABLEID, new String[] {
448 			ColumnCS_Name.COLUMN_ID,
449 			ColumnCS_Pct.COLUMN_ID,
450 			ColumnCS_Count.COLUMN_ID,
451 			ColumnCS_Received.COLUMN_ID,
452 			ColumnCS_Sent.COLUMN_ID,
453 			ColumnCS_Discarded.COLUMN_ID,
454 		});
455 	}
456 
tableViewDestroyed()457 	public void tableViewDestroyed() {
458 	}
459 
460 
initAndLoad()461 	private void initAndLoad() {
462 		synchronized (mapData) {
463 			Map map = FileUtil.readResilientConfigFile(CONFIG_FILE);
464 
465 			totalTime = MapUtils.getMapLong(map, "time", 0);
466 
467 			lastAdd = MapUtils.getMapLong(map, "lastadd", 0);
468 			if (lastAdd != 0) {
469 				calendar.setTimeInMillis(lastAdd);
470 				lastAddMonth = calendar.get(Calendar.MONTH);
471 
472 				Map mapBloom = MapUtils.getMapMap(map, "bloomfilter", null);
473 				if (mapBloom != null) {
474 					bloomFilter = BloomFilterFactory.deserialiseFromMap(mapBloom);
475 				}
476 				mapBloom = MapUtils.getMapMap(map, "bloomfilterPeerId", null);
477 				if (mapBloom != null) {
478 					bloomFilterPeerId = BloomFilterFactory.deserialiseFromMap(mapBloom);
479 				}
480 			}
481 			if (bloomFilter == null) {
482 				bloomFilter = BloomFilterFactory.createRotating(
483 						BloomFilterFactory.createAddOnly(BLOOMFILTER_SIZE), 2);
484 			}
485 			if (bloomFilterPeerId == null) {
486 				bloomFilterPeerId = BloomFilterFactory.createRotating(
487 						BloomFilterFactory.createAddOnly(BLOOMFILTER_PEERID_SIZE), 2);
488 			}
489 
490 			overall = new ClientStatsOverall();
491 
492 			List listSavedData = MapUtils.getMapList(map, "data", null);
493 			if (listSavedData != null) {
494 				for (Object val : listSavedData) {
495 					try {
496 						Map mapVal = (Map) val;
497 						if (mapVal != null) {
498 							ClientStatsDataSource ds = new ClientStatsDataSource(mapVal);
499 							ds.overall = overall;
500 
501 							if (!mapData.containsKey(ds.client)) {
502 								mapData.put(ds.client, ds);
503 								overall.count += ds.count;
504 							}
505 						}
506 
507 					} catch (Exception e) {
508 						// ignore
509 					}
510 				}
511 			}
512 		}
513 	}
514 
save(String filename)515 	private void save(String filename) {
516 		Map<String, Object> map = new HashMap<String, Object>();
517 		synchronized (mapData) {
518 			map.put("data", new ArrayList(mapData.values()));
519 			map.put("bloomfilter", bloomFilter.serialiseToMap());
520 			map.put("bloomfilterPeerId", bloomFilterPeerId.serialiseToMap());
521 			map.put("lastadd", SystemTime.getCurrentTime());
522 			if (startedListeningOn > 0) {
523 				map.put("time", totalTime
524 						+ (SystemTime.getCurrentTime() - startedListeningOn));
525 			} else {
526 				map.put("time", totalTime);
527 			}
528 		}
529 		FileUtil.writeResilientConfigFile(filename, map);
530 	}
531 
tableViewInitialized()532 	public void tableViewInitialized() {
533 		synchronized (mapData) {
534 			if (mapData.values().size() > 0) {
535 				tv.addDataSources(mapData.values().toArray(new ClientStatsDataSource[0]));
536 			}
537 		}
538 	}
539 
register(AzureusCore core)540 	protected void register(AzureusCore core) {
541 		this.core = core;
542 		core.getGlobalManager().addListener(this);
543 		synchronized (mapData) {
544 			startedListeningOn = SystemTime.getCurrentTime();
545 		}
546 		registered = true;
547 	}
548 
549 	// @see org.gudy.azureus2.core3.global.GlobalManagerListener#destroyInitiated()
destroyInitiated()550 	public void destroyInitiated() {
551 		if (core == null) {
552 			return;
553 		}
554 		core.getGlobalManager().removeListener(this);
555 		List downloadManagers = core.getGlobalManager().getDownloadManagers();
556 		for (Object object : downloadManagers) {
557 			((DownloadManager) object).removePeerListener(this);
558 		}
559 		registered = false;
560 		save(CONFIG_FILE);
561 	}
562 
563 	// @see org.gudy.azureus2.core3.global.GlobalManagerListener#destroyed()
destroyed()564 	public void destroyed() {
565 	}
566 
downloadManagerAdded(DownloadManager dm)567 	public void downloadManagerAdded(DownloadManager dm) {
568 		if (!dm.getDownloadState().getFlag(DownloadManagerState.FLAG_LOW_NOISE)) {
569 			dm.addPeerListener(this, true);
570 		}
571 	}
572 
downloadManagerRemoved(DownloadManager dm)573 	public void downloadManagerRemoved(DownloadManager dm) {
574 		dm.removePeerListener(this);
575 	}
576 
seedingStatusChanged(boolean seedingOnlyMode, boolean potentiallySeedingOnlyMode)577 	public void seedingStatusChanged(boolean seedingOnlyMode,
578 			boolean potentiallySeedingOnlyMode) {
579 	}
580 
peerAdded(PEPeer peer)581 	public void peerAdded(PEPeer peer) {
582 		peer.addListener(new PEPeerListener() {
583 
584 			public void stateChanged(PEPeer peer, int newState) {
585 				if (newState == PEPeer.TRANSFERING) {
586 					addPeer(peer);
587 				} else if (newState == PEPeer.CLOSING
588 						|| newState == PEPeer.DISCONNECTED) {
589 					peer.removeListener(this);
590 				}
591 			}
592 
593 			public void sentBadChunk(PEPeer peer, int pieceNum, int totalBadChunks) {
594 			}
595 
596 			public void removeAvailability(PEPeer peer, BitFlags peerHavePieces) {
597 			}
598 
599 			public void addAvailability(PEPeer peer, BitFlags peerHavePieces) {
600 			}
601 		});
602 	}
603 
addPeer(PEPeer peer)604 	protected void addPeer(PEPeer peer) {
605 		byte[] bloomId;
606 		long now = SystemTime.getCurrentTime();
607 
608 		// Bloom Filter is based on the first 8 bytes of peer id + ip address
609 		// This captures more duplicates than peer id because most clients
610 		// randomize their peer id on restart.  IP address, however, changes
611 		// less often.
612 		byte[] address = null;
613 		byte[] peerId = peer.getId();
614 		InetAddress ip = peer.getAlternativeIPv6();
615 		if (ip == null) {
616 			try {
617 				ip = AddressUtils.getByName(peer.getIp());
618 				address = ip.getAddress();
619 			} catch (Throwable e) {
620 				String ipString = peer.getIp();
621 				if (ipString != null) {
622 					address = ByteFormatter.intToByteArray(ipString.hashCode());
623 				}
624 			}
625 		} else {
626 			address = ip.getAddress();
627 		}
628 		if (address == null) {
629 			bloomId = peerId;
630 		} else {
631 			bloomId = new byte[8 + address.length];
632 			System.arraycopy(peerId, 0, bloomId, 0, 8);
633 			System.arraycopy(address, 0, bloomId, 8, address.length);
634 		}
635 
636 		synchronized (mapData) {
637 			// break on month.. assume user didn't last use this on the same month in a different year
638 			calendar.setTimeInMillis(now);
639 			int thisMonth = calendar.get(Calendar.MONTH);
640 			if (thisMonth != lastAddMonth) {
641 				if (lastAddMonth == 0) {
642 					lastAddMonth = thisMonth;
643 				} else {
644 					String s = new SimpleDateFormat("yyyy-MM").format(new Date(lastAdd));
645 					String filename = CONFIG_FILE_ARCHIVE.replace("%1", s);
646 					save(filename);
647 
648 					lastAddMonth = thisMonth;
649 					lastAdd = 0;
650 					bloomFilter = BloomFilterFactory.createRotating(
651 							BloomFilterFactory.createAddOnly(BLOOMFILTER_SIZE), 2);
652 					bloomFilterPeerId = BloomFilterFactory.createRotating(
653 							BloomFilterFactory.createAddOnly(BLOOMFILTER_PEERID_SIZE), 2);
654 					overall = new ClientStatsOverall();
655 					mapData.clear();
656 					if (tv != null) {
657 						tv.removeAllTableRows();
658 					}
659 					totalTime = 0;
660 					startedListeningOn = 0;
661 				}
662 			}
663 
664 			String id = getID(peer);
665 			ClientStatsDataSource stat;
666 			stat = mapData.get(id);
667 			boolean needNew = stat == null;
668 			if (needNew) {
669 				stat = new ClientStatsDataSource();
670 				stat.overall = overall;
671 				stat.client = id;
672 				mapData.put(id, stat);
673 			}
674 
675 			boolean inBloomFilter = bloomFilter.contains(bloomId) || bloomFilterPeerId.contains(peerId);
676 
677 			if (!inBloomFilter) {
678 				bloomFilter.add(bloomId);
679 				bloomFilterPeerId.add(peerId);
680 
681 				lastAdd = now;
682 				synchronized (overall) {
683 
684 					overall.count++;
685 				}
686 				stat.count++;
687 			}
688 
689 			stat.current++;
690 
691 			long existingBytesReceived = peer.getStats().getTotalDataBytesReceived();
692 			long existingBytesSent = peer.getStats().getTotalDataBytesSent();
693 			long existingBytesDiscarded = peer.getStats().getTotalBytesDiscarded();
694 
695 			if (existingBytesReceived > 0) {
696 				stat.bytesReceived -= existingBytesReceived;
697 				if (stat.bytesReceived < 0) {
698 					stat.bytesReceived = 0;
699 				}
700 			}
701 			if (existingBytesSent > 0) {
702 				stat.bytesSent -= existingBytesSent;
703 				if (stat.bytesSent < 0) {
704 					stat.bytesSent = 0;
705 				}
706 			}
707 			if (existingBytesDiscarded > 0) {
708 				stat.bytesDiscarded -= existingBytesDiscarded;
709 				if (stat.bytesDiscarded < 0) {
710 					stat.bytesDiscarded = 0;
711 				}
712 			}
713 
714 			if (peer instanceof PEPeerTransport) {
715 				PeerItem identity = ((PEPeerTransport) peer).getPeerItemIdentity();
716 				if (identity != null) {
717 					String network = identity.getNetwork();
718 					if (network != null) {
719 						Map<String, Object> map = stat.perNetworkStats.get(network);
720 						if (map == null) {
721 							map = new HashMap<String, Object>();
722 							stat.perNetworkStats.put(network, map);
723 						}
724 						if (!inBloomFilter) {
725   						long count = MapUtils.getMapLong(map, "count", 0);
726   						map.put("count", count + 1);
727 						}
728 
729 						if (existingBytesReceived > 0) {
730 							long bytesReceived = MapUtils.getMapLong(map, "bytesReceived",
731 									0);
732 							bytesReceived -= existingBytesReceived;
733 							if (bytesReceived < 0) {
734 								bytesReceived = 0;
735 							}
736 							map.put("bytesReceived", bytesReceived);
737 						}
738 						if (existingBytesSent > 0) {
739 							long bytesSent = MapUtils.getMapLong(map, "bytesSent", 0);
740 							bytesSent -= existingBytesSent;
741 							if (bytesSent < 0) {
742 								bytesSent = 0;
743 							}
744 							map.put("bytesSent", bytesSent);
745 						}
746 						if (existingBytesDiscarded > 0) {
747 							long bytesDiscarded = MapUtils.getMapLong(map,
748 									"bytesDiscarded", 0);
749 							bytesDiscarded -= existingBytesDiscarded;
750 							if (bytesDiscarded < 0) {
751 								bytesDiscarded = 0;
752 							}
753 							map.put("bytesDiscarded", bytesDiscarded);
754 						}
755 
756 					}
757 				}
758 			}
759 
760 			if (tv != null) {
761   			if (needNew) {
762   				tv.addDataSource(stat);
763   			} else {
764   				TableRowCore row = tv.getRow(stat);
765   				if (row != null) {
766   					row.invalidate();
767   				}
768   			}
769 			}
770 		}
771 	}
772 
peerManagerAdded(PEPeerManager manager)773 	public void peerManagerAdded(PEPeerManager manager) {
774 	}
775 
peerManagerRemoved(PEPeerManager manager)776 	public void peerManagerRemoved(PEPeerManager manager) {
777 	}
778 
peerManagerWillBeAdded(PEPeerManager manager)779 	public void peerManagerWillBeAdded(PEPeerManager manager) {
780 	}
781 
peerRemoved(PEPeer peer)782 	public void peerRemoved(PEPeer peer) {
783 		synchronized (mapData) {
784 			ClientStatsDataSource stat = mapData.get(getID(peer));
785 			if (peer.getStats().getTotalDataBytesSent() > 0)
786 			if (stat != null) {
787 				stat.current--;
788 
789 				String network = null;
790 				if (peer instanceof PEPeerTransport) {
791 					PeerItem identity = ((PEPeerTransport) peer).getPeerItemIdentity();
792 					if (identity != null) {
793 						network = identity.getNetwork();
794 					}
795 				}
796 
797 
798 				stat.bytesReceived += peer.getStats().getTotalDataBytesReceived();
799 				stat.bytesSent += peer.getStats().getTotalDataBytesSent();
800 				stat.bytesDiscarded += peer.getStats().getTotalBytesDiscarded();
801 
802 				if (network != null) {
803 					Map<String, Object> map = stat.perNetworkStats.get(network);
804 					if (map == null) {
805 						map = new HashMap<String, Object>();
806 						stat.perNetworkStats.put(network, map);
807 					}
808 					long bytesReceived = MapUtils.getMapLong(map, "bytesReceived", 0);
809 					map.put("bytesReceived", bytesReceived
810 							+ peer.getStats().getTotalDataBytesReceived());
811 					long bytesSent = MapUtils.getMapLong(map, "bytesSent", 0);
812 					map.put("bytesSent", bytesSent
813 							+ peer.getStats().getTotalDataBytesSent());
814 					long bytesDiscarded = MapUtils.getMapLong(map, "bytesDiscarded", 0);
815 					map.put("bytesDiscarded", bytesDiscarded
816 							+ peer.getStats().getTotalBytesDiscarded());
817 				}
818 
819 				if (tv != null) {
820   				TableRowCore row = tv.getRow(stat);
821   				if (row != null) {
822   					row.invalidate();
823   				}
824 				}
825 			}
826 		}
827 	}
828 
getID(PEPeer peer)829 	private String getID(PEPeer peer) {
830 		String s = peer.getClientNameFromPeerID();
831 		if (s == null) {
832 			s = peer.getClient();
833 			if (s.startsWith("HTTP Seed")) {
834 				return "HTTP Seed";
835 			}
836 		}
837 		return s.replaceAll(" v?[0-9._]+", "");
838 	}
839 }
840