• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-R7000-V1.0.7.12_1.2.5/ap/gpl/transmission/transmission-2.73/web/javascript/
1/**
2 * Copyright �� Mnemosyne LLC
3 *
4 * This file is licensed under the GPLv2.
5 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6 */
7
8function Torrent(data)
9{
10	this.initialize(data);
11}
12
13/***
14****
15****  Constants
16****
17***/
18
19// Torrent.fields.status
20Torrent._StatusStopped         = 0;
21Torrent._StatusCheckWait       = 1;
22Torrent._StatusCheck           = 2;
23Torrent._StatusDownloadWait    = 3;
24Torrent._StatusDownload        = 4;
25Torrent._StatusSeedWait        = 5;
26Torrent._StatusSeed            = 6;
27
28// Torrent.fields.seedRatioMode
29Torrent._RatioUseGlobal        = 0;
30Torrent._RatioUseLocal         = 1;
31Torrent._RatioUnlimited        = 2;
32
33// Torrent.fields.error
34Torrent._ErrNone               = 0;
35Torrent._ErrTrackerWarning     = 1;
36Torrent._ErrTrackerError       = 2;
37Torrent._ErrLocalError         = 3;
38
39// TrackerStats' announceState
40Torrent._TrackerInactive       = 0;
41Torrent._TrackerWaiting        = 1;
42Torrent._TrackerQueued         = 2;
43Torrent._TrackerActive         = 3;
44
45
46Torrent.Fields = { };
47
48// commonly used fields which only need to be loaded once,
49// either on startup or when a magnet finishes downloading its metadata
50// finishes downloading its metadata
51Torrent.Fields.Metadata = [
52	'addedDate',
53	'name',
54	'totalSize'
55];
56
57// commonly used fields which need to be periodically refreshed
58Torrent.Fields.Stats = [
59	'error',
60	'errorString',
61	'eta',
62	'isFinished',
63	'isStalled',
64	'leftUntilDone',
65	'metadataPercentComplete',
66	'peersConnected',
67	'peersGettingFromUs',
68	'peersSendingToUs',
69	'percentDone',
70	'queuePosition',
71	'rateDownload',
72	'rateUpload',
73	'recheckProgress',
74	'seedRatioMode',
75	'seedRatioLimit',
76	'sizeWhenDone',
77	'status',
78	'trackers',
79	'downloadDir',
80	'uploadedEver',
81	'uploadRatio',
82	'webseedsSendingToUs'
83];
84
85// fields used by the inspector which only need to be loaded once
86Torrent.Fields.InfoExtra = [
87	'comment',
88	'creator',
89	'dateCreated',
90	'files',
91	'hashString',
92	'isPrivate',
93	'pieceCount',
94	'pieceSize'
95];
96
97// fields used in the inspector which need to be periodically refreshed
98Torrent.Fields.StatsExtra = [
99	'activityDate',
100	'corruptEver',
101	'desiredAvailable',
102	'downloadedEver',
103	'fileStats',
104	'haveUnchecked',
105	'haveValid',
106	'peers',
107	'startDate',
108	'trackerStats'
109];
110
111/***
112****
113****  Methods
114****
115***/
116
117Torrent.prototype =
118{
119	initialize: function(data)
120	{
121		this.fields = {};
122		this.fieldObservers = {};
123		this.refresh (data);
124	},
125
126	notifyOnFieldChange: function(field, callback) {
127		this.fieldObservers[field] = this.fieldObservers[field] || [];
128		this.fieldObservers[field].push(callback);
129	},
130
131	setField: function(o, name, value)
132	{
133		var i, observer;
134
135		if (o[name] === value)
136			return false;
137		if (o == this.fields && this.fieldObservers[name] && this.fieldObservers[name].length) {
138			for (i=0; observer=this.fieldObservers[name][i]; ++i) {
139				observer.call(this, value, o[name], name);
140			}
141		}
142		o[name] = value;
143		return true;
144	},
145
146	// fields.files is an array of unions of RPC's "files" and "fileStats" objects.
147	updateFiles: function(files)
148	{
149		var changed = false,
150		    myfiles = this.fields.files || [],
151		    keys = [ 'length', 'name', 'bytesCompleted', 'wanted', 'priority' ],
152		    i, f, j, key, myfile;
153
154		for (i=0; f=files[i]; ++i) {
155			myfile = myfiles[i] || {};
156			for (j=0; key=keys[j]; ++j)
157				if(key in f)
158					changed |= this.setField(myfile,key,f[key]);
159			myfiles[i] = myfile;
160		}
161		this.fields.files = myfiles;
162		return changed;
163	},
164
165	collateTrackers: function(trackers)
166	{
167		var i, t, announces = [];
168
169		for (i=0; t=trackers[i]; ++i)
170			announces.push(t.announce.toLowerCase());
171		return announces.join('\t');
172	},
173
174	refreshFields: function(data)
175	{
176		var key,
177		    changed = false;
178
179		for (key in data) {
180			switch (key) {
181				case 'files':
182				case 'fileStats': // merge files and fileStats together
183					changed |= this.updateFiles(data[key]);
184					break;
185				case 'trackerStats': // 'trackerStats' is a superset of 'trackers'...
186					changed |= this.setField(this.fields,'trackers',data[key]);
187					break;
188				case 'trackers': // ...so only save 'trackers' if we don't have it already
189					if (!(key in this.fields))
190						changed |= this.setField(this.fields,key,data[key]);
191					break;
192				default:
193					changed |= this.setField(this.fields,key,data[key]);
194			}
195		}
196
197		return changed;
198	},
199
200	refresh: function(data)
201	{
202		if (this.refreshFields(data))
203			$(this).trigger('dataChanged', this);
204	},
205
206	/****
207	*****
208	****/
209
210	// simple accessors
211	getComment: function() { return this.fields.comment; },
212	getCreator: function() { return this.fields.creator; },
213	getDateAdded: function() { return this.fields.addedDate; },
214	getDateCreated: function() { return this.fields.dateCreated; },
215	getDesiredAvailable: function() { return this.fields.desiredAvailable; },
216	getDownloadDir: function() { return this.fields.downloadDir; },
217	getDownloadSpeed: function() { return this.fields.rateDownload; },
218	getDownloadedEver: function() { return this.fields.downloadedEver; },
219	getError: function() { return this.fields.error; },
220	getErrorString: function() { return this.fields.errorString; },
221	getETA: function() { return this.fields.eta; },
222	getFailedEver: function(i) { return this.fields.corruptEver; },
223	getFile: function(i) { return this.fields.files[i]; },
224	getFileCount: function() { return this.fields.files ? this.fields.files.length : 0; },
225	getHashString: function() { return this.fields.hashString; },
226	getHave: function() { return this.getHaveValid() + this.getHaveUnchecked() },
227	getHaveUnchecked: function() { return this.fields.haveUnchecked; },
228	getHaveValid: function() { return this.fields.haveValid; },
229	getId: function() { return this.fields.id; },
230	getLastActivity: function() { return this.fields.activityDate; },
231	getLeftUntilDone: function() { return this.fields.leftUntilDone; },
232	getMetadataPercentComplete: function() { return this.fields.metadataPercentComplete; },
233	getName: function() { return this.fields.name || 'Unknown'; },
234	getPeers: function() { return this.fields.peers; },
235	getPeersConnected: function() { return this.fields.peersConnected; },
236	getPeersGettingFromUs: function() { return this.fields.peersGettingFromUs; },
237	getPeersSendingToUs: function() { return this.fields.peersSendingToUs; },
238	getPieceCount: function() { return this.fields.pieceCount; },
239	getPieceSize: function() { return this.fields.pieceSize; },
240	getPrivateFlag: function() { return this.fields.isPrivate; },
241	getQueuePosition: function() { return this.fields.queuePosition; },
242	getRecheckProgress: function() { return this.fields.recheckProgress; },
243	getSeedRatioLimit: function() { return this.fields.seedRatioLimit; },
244	getSeedRatioMode: function() { return this.fields.seedRatioMode; },
245	getSizeWhenDone: function() { return this.fields.sizeWhenDone; },
246	getStartDate: function() { return this.fields.startDate; },
247	getStatus: function() { return this.fields.status; },
248	getTotalSize: function() { return this.fields.totalSize; },
249	getTrackers: function() { return this.fields.trackers; },
250	getUploadSpeed: function() { return this.fields.rateUpload; },
251	getUploadRatio: function() { return this.fields.uploadRatio; },
252	getUploadedEver: function() { return this.fields.uploadedEver; },
253	getWebseedsSendingToUs: function() { return this.fields.webseedsSendingToUs; },
254	isFinished: function() { return this.fields.isFinished; },
255
256	// derived accessors
257	hasExtraInfo: function() { return 'hashString' in this.fields; },
258	isSeeding: function() { return this.getStatus() === Torrent._StatusSeed; },
259	isStopped: function() { return this.getStatus() === Torrent._StatusStopped; },
260	isChecking: function() { return this.getStatus() === Torrent._StatusCheck; },
261	isDownloading: function() { return this.getStatus() === Torrent._StatusDownload; },
262	isDone: function() { return this.getLeftUntilDone() < 1; },
263	needsMetaData: function(){ return this.getMetadataPercentComplete() < 1; },
264	getActivity: function() { return this.getDownloadSpeed() + this.getUploadSpeed(); },
265	getPercentDoneStr: function() { return Transmission.fmt.percentString(100*this.getPercentDone()); },
266	getPercentDone: function() { return this.fields.percentDone; },
267	getStateString: function() {
268		switch(this.getStatus()) {
269			case Torrent._StatusStopped:        return this.isFinished() ? 'Seeding complete' : 'Paused';
270			case Torrent._StatusCheckWait:      return 'Queued for verification';
271			case Torrent._StatusCheck:          return 'Verifying local data';
272			case Torrent._StatusDownloadWait:   return 'Queued for download';
273			case Torrent._StatusDownload:       return 'Downloading';
274			case Torrent._StatusSeedWait:       return 'Queued for seeding';
275			case Torrent._StatusSeed:           return 'Seeding';
276			case null:
277			case undefined:                     return 'Unknown';
278			default:                            return 'Error';
279		}
280	},
281	seedRatioLimit: function(controller){
282		switch(this.getSeedRatioMode()) {
283			case Torrent._RatioUseGlobal: return controller.seedRatioLimit();
284			case Torrent._RatioUseLocal:  return this.getSeedRatioLimit();
285			default:                      return -1;
286		}
287	},
288	getErrorMessage: function() {
289		var str = this.getErrorString();
290		switch(this.getError()) {
291			case Torrent._ErrTrackerWarning:
292				return 'Tracker returned a warning: ' + str;
293			case Torrent._ErrTrackerError:
294				return 'Tracker returned an error: ' + str;
295			case Torrent._ErrLocalError:
296				return 'Error: ' + str;
297			default:
298				return null;
299		}
300	},
301	getCollatedName: function() {
302		var f = this.fields;
303		if (!f.collatedName && f.name)
304			f.collatedName = f.name.toLowerCase();
305		return f.collatedName || '';
306	},
307	getCollatedTrackers: function() {
308		var f = this.fields;
309		if (!f.collatedTrackers && f.trackers)
310			f.collatedTrackers = this.collateTrackers(f.trackers);
311		return f.collatedTrackers || '';
312	},
313
314	/****
315	*****
316	****/
317
318	testState: function(state)
319	{
320		var s = this.getStatus();
321
322		switch(state)
323		{
324			case Prefs._FilterActive:
325				return this.getPeersGettingFromUs() > 0
326				    || this.getPeersSendingToUs() > 0
327				    || this.getWebseedsSendingToUs() > 0
328				    || this.isChecking();
329			case Prefs._FilterSeeding:
330				return (s === Torrent._StatusSeed)
331				    || (s === Torrent._StatusSeedWait);
332			case Prefs._FilterDownloading:
333				return (s === Torrent._StatusDownload)
334				    || (s === Torrent._StatusDownloadWait);
335			case Prefs._FilterPaused:
336				return this.isStopped();
337			case Prefs._FilterFinished:
338				return this.isFinished();
339			default:
340				return true;
341		}
342	},
343
344	/**
345	 * @param filter one of Prefs._Filter*
346	 * @param search substring to look for, or null
347	 * @return true if it passes the test, false if it fails
348	 */
349	test: function(state, search, tracker)
350	{
351		// flter by state...
352		var pass = this.testState(state);
353
354		// maybe filter by text...
355		if (pass && search && search.length)
356			pass = this.getCollatedName().indexOf(search.toLowerCase()) !== -1;
357
358		// maybe filter by tracker...
359		if (pass && tracker && tracker.length)
360			pass = this.getCollatedTrackers().indexOf(tracker) !== -1;
361
362		return pass;
363	}
364};
365
366
367/***
368****
369****  SORTING
370****
371***/
372
373Torrent.compareById = function(ta, tb)
374{
375	return ta.getId() - tb.getId();
376};
377Torrent.compareByName = function(ta, tb)
378{
379	return ta.getCollatedName().localeCompare(tb.getCollatedName())
380	    || Torrent.compareById(ta, tb);
381};
382Torrent.compareByQueue = function(ta, tb)
383{
384	return ta.getQueuePosition() - tb.getQueuePosition();
385};
386Torrent.compareByAge = function(ta, tb)
387{
388	var a = ta.getDateAdded(),
389	    b = tb.getDateAdded();
390
391	return (b - a) || Torrent.compareByQueue(ta, tb);
392};
393Torrent.compareByState = function(ta, tb)
394{
395	var a = ta.getStatus(),
396	    b = tb.getStatus();
397
398	return (b - a) || Torrent.compareByQueue(ta, tb);
399};
400Torrent.compareByActivity = function(ta, tb)
401{
402	var a = ta.getActivity(),
403	    b = tb.getActivity();
404
405	return (b - a) || Torrent.compareByState(ta, tb);
406};
407Torrent.compareByRatio = function(ta, tb)
408{
409	var a = ta.getUploadRatio(),
410	    b = tb.getUploadRatio();
411
412	if (a < b) return 1;
413	if (a > b) return -1;
414	return Torrent.compareByState(ta, tb);
415};
416Torrent.compareByProgress = function(ta, tb)
417{
418	var a = ta.getPercentDone(),
419	    b = tb.getPercentDone();
420
421	return (a - b) || Torrent.compareByRatio(ta, tb);
422};
423
424Torrent.compareBySize = function(ta, tb)
425{
426    var a = ta.getTotalSize(),
427        b = tb.getTotalSize();
428
429    return (a - b) || Torrent.compareByName(ta, tb);
430}
431
432Torrent.compareTorrents = function(a, b, sortMethod, sortDirection)
433{
434	var i;
435
436	switch(sortMethod)
437	{
438		case Prefs._SortByActivity:
439			i = Torrent.compareByActivity(a,b);
440			break;
441		case Prefs._SortByAge:
442			i = Torrent.compareByAge(a,b);
443			break;
444		case Prefs._SortByQueue:
445			i = Torrent.compareByQueue(a,b);
446			break;
447		case Prefs._SortByProgress:
448			i = Torrent.compareByProgress(a,b);
449			break;
450        case Prefs._SortBySize:
451            i = Torrent.compareBySize(a,b);
452            break;
453		case Prefs._SortByState:
454			i = Torrent.compareByState(a,b);
455			break;
456		case Prefs._SortByRatio:
457			i = Torrent.compareByRatio(a,b);
458			break;
459		default:
460			i = Torrent.compareByName(a,b);
461			break;
462	}
463
464	if (sortDirection === Prefs._SortDescending)
465		i = -i;
466
467	return i;
468};
469
470/**
471 * @param torrents an array of Torrent objects
472 * @param sortMethod one of Prefs._SortBy*
473 * @param sortDirection Prefs._SortAscending or Prefs._SortDescending
474 */
475Torrent.sortTorrents = function(torrents, sortMethod, sortDirection)
476{
477	switch(sortMethod)
478	{
479		case Prefs._SortByActivity:
480			torrents.sort(this.compareByActivity);
481			break;
482		case Prefs._SortByAge:
483			torrents.sort(this.compareByAge);
484			break;
485		case Prefs._SortByQueue:
486			torrents.sort(this.compareByQueue);
487			break;
488		case Prefs._SortByProgress:
489			torrents.sort(this.compareByProgress);
490			break;
491        case Prefs._SortBySize:
492            torrents.sort(this.compareBySize);
493            break;
494		case Prefs._SortByState:
495			torrents.sort(this.compareByState);
496			break;
497		case Prefs._SortByRatio:
498			torrents.sort(this.compareByRatio);
499			break;
500		default:
501			torrents.sort(this.compareByName);
502			break;
503	}
504
505	if (sortDirection === Prefs._SortDescending)
506		torrents.reverse();
507
508	return torrents;
509};
510