Changeset 14523


Ignore:
Timestamp:
May 5, 2015, 7:12:48 PM (6 years ago)
Author:
mikedld
Message:

#5944: Unify indentation style and strip trailing whitespaces across JS code, no functional changes (patch by xzcvczx + a lot more)

Location:
trunk/web/javascript
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/web/javascript/common.js

    r14518 r14523  
    2929        this.find('.ui-tab-dialog-close > a').css({'float':'none','padding':'0'});
    3030        var tabul = this.find('ul:first');
    31         this.parent().addClass('ui-tabs').prepend(tabul).draggable('option','handle',tabul); 
     31        this.parent().addClass('ui-tabs').prepend(tabul).draggable('option','handle',tabul);
    3232        this.siblings('.ui-dialog-titlebar').remove();
    3333        tabul.addClass('ui-dialog-titlebar');
     
    247247                        var key = e.which || e.keyCode;
    248248                        return !e.shiftKey && !e.altKey && !e.ctrlKey &&
    249                                 // numbers   
     249                                // numbers
    250250                                key >= 48 && key <= 57 ||
    251251                                // Numeric keypad
  • trunk/web/javascript/dialog.js

    r14511 r14523  
    1616         */
    1717        initialize: function() {
    18                
     18
    1919                /*
    2020                 * Private Interface Variables
     
    2626                this._confirm_button = $('#dialog_confirm_button');
    2727                this._callback = null;
    28                
     28
    2929                // Observe the buttons
    3030                this._cancel_button.bind('click', {dialog: this}, this.onCancelClicked);
     
    109109                this._container.show();
    110110        }
    111        
     111
    112112
    113113}
  • trunk/web/javascript/file-row.js

    r13598 r14523  
    4646                var pct = 100 * (fields.size ? (fields.have / fields.size) : 1.0),
    4747                    c = [ Transmission.fmt.size(fields.have),
    48                           ' of ',
    49                           Transmission.fmt.size(fields.size),
    50                           ' (',
    51                           Transmission.fmt.percentString(pct),
    52                           '%)' ].join('');
     48                          ' of ',
     49                          Transmission.fmt.size(fields.size),
     50                          ' (',
     51                          Transmission.fmt.percentString(pct),
     52                          '%)' ].join('');
    5353                setTextContent(elements.progress, c);
    5454        },
  • trunk/web/javascript/formatter.js

    r13556 r14523  
    280280                },
    281281
    282                 peerStatus: function( flagStr ) 
    283                 { 
    284                         var formattedFlags = []; 
     282                peerStatus: function( flagStr )
     283                {
     284                        var formattedFlags = [];
    285285                        for (var i=0, flag; flag=flagStr[i]; ++i)
    286286                        {
     
    302302                                }
    303303
    304                                 if (!explanation) { 
    305                                         formattedFlags.push(flag); 
    306                                 } else { 
    307                                         formattedFlags.push("<span title=\"" + flag + ': ' + explanation + "\">" + flag + "</span>"); 
    308                                 } 
    309                         } 
    310                         return formattedFlags.join(''); 
     304                                if (!explanation) {
     305                                        formattedFlags.push(flag);
     306                                } else {
     307                                        formattedFlags.push("<span title=\"" + flag + ': ' + explanation + "\">" + flag + "</span>");
     308                                }
     309                        }
     310                        return formattedFlags.join('');
    311311                }
    312312        }
  • trunk/web/javascript/inspector.js

    r14509 r14523  
    88function Inspector(controller) {
    99
    10     var data = {
    11         controller: null,
    12         elements: { },
    13         torrents: [ ]
    14     },
    15 
    16     needsExtraInfo = function (torrents) {
    17         var i, id, tor;
    18 
    19         for (i = 0; tor = torrents[i]; i++)
    20             if (!tor.hasExtraInfo())
    21                 return true;
    22 
    23         return false;
    24     },
    25 
    26     refreshTorrents = function () {
    27         var fields,
    28             ids = $.map(data.torrents.slice(0), function (t) {return t.getId();});
    29 
    30         if (ids && ids.length)
    31         {
    32             fields = ['id'].concat(Torrent.Fields.StatsExtra);
    33 
    34             if (needsExtraInfo(data.torrents))
    35                 $.merge(fields, Torrent.Fields.InfoExtra);
    36 
    37             data.controller.updateTorrents(ids, fields);
    38         }
    39     },
    40 
    41     onTabClicked = function (ev) {
    42         var tab = ev.currentTarget;
    43 
    44         if (isMobileDevice)
    45             ev.stopPropagation();
    46 
    47         // select this tab and deselect the others
    48         $(tab).addClass('selected').siblings().removeClass('selected');
    49 
    50         // show this tab and hide the others
    51         $('#' + tab.id.replace('tab','page')).show().siblings('.inspector-page').hide();
    52 
    53         updateInspector();
    54     },
    55 
    56     updateInspector = function () {
    57         var e = data.elements,
    58             torrents = data.torrents,
    59             name;
    60 
    61         // update the name, which is shown on all the pages
    62         if (!torrents || !torrents.length)
    63             name = 'No Selection';
    64         else if(torrents.length === 1)
    65             name = torrents[0].getName();
    66         else
    67             name = '' + torrents.length+' Transfers Selected';
    68         setTextContent(e.name_lb, name || na);
    69 
    70         // update the visible page
    71         if ($(e.info_page).is(':visible'))
    72             updateInfoPage();
    73         else if ($(e.peers_page).is(':visible'))
    74             updatePeersPage();
    75         else if ($(e.trackers_page).is(':visible'))
    76             updateTrackersPage();
    77         else if ($(e.files_page).is(':visible'))
    78             updateFilesPage();
    79     },
    80 
    81     /****
    82     *****  GENERAL INFO PAGE
    83     ****/
    84 
    85     updateInfoPage = function () {
    86         var torrents = data.torrents,
    87             e = data.elements,
    88             fmt = Transmission.fmt,
    89             none = 'None',
    90             mixed = 'Mixed',
    91             unknown = 'Unknown',
    92             isMixed, allPaused, allFinished,
    93             str,
    94             baseline, it, s, i, t,
    95             sizeWhenDone = 0,
    96             leftUntilDone = 0,
    97             available = 0,
    98             haveVerified = 0,
    99             haveUnverified = 0,
    100             verifiedPieces = 0,
    101             stateString,
    102             latest,
    103             pieces,
    104             size,
    105             pieceSize,
    106             creator, mixed_creator,
    107             date, mixed_date,
    108             v, u, f, d, pct,
    109             uri,
    110             now = Date.now();
    111 
    112         //
    113         //  state_lb
    114         //
    115 
    116         if(torrents.length <1)
    117             str = none;
    118         else {
    119             isMixed = false;
    120             allPaused = true;
    121             allFinished = true;
    122 
    123             baseline = torrents[0].getStatus();
    124             for(i=0; t=torrents[i]; ++i) {
    125                 it = t.getStatus();
    126                 if(it != baseline)
    127                     isMixed = true;
    128                 if(!t.isStopped())
    129                     allPaused = allFinished = false;
    130                 if(!t.isFinished())
    131                     allFinished = false;
    132             }
    133             if( isMixed )
    134                 str = mixed;
    135             else if( allFinished )
    136                 str = 'Finished';
    137             else if( allPaused )
    138                 str = 'Paused';
    139             else
    140                 str = torrents[0].getStateString();
    141         }
    142         setTextContent(e.state_lb, str);
    143         stateString = str;
    144 
    145         //
    146         //  have_lb
    147         //
    148 
    149         if(torrents.length < 1)
    150             str = none;
    151         else {
    152             baseline = torrents[0].getStatus();
    153             for(i=0; t=torrents[i]; ++i) {
    154                 if(!t.needsMetaData()) {
    155                     haveUnverified += t.getHaveUnchecked();
    156                     v = t.getHaveValid();
    157                     haveVerified += v;
    158                     if(t.getPieceSize())
    159                         verifiedPieces += v / t.getPieceSize();
    160                     sizeWhenDone += t.getSizeWhenDone();
    161                     leftUntilDone += t.getLeftUntilDone();
    162                     available += (t.getHave()) + t.getDesiredAvailable();
    163                 }
    164             }
    165 
    166             d = 100.0 * ( sizeWhenDone ? ( sizeWhenDone - leftUntilDone ) / sizeWhenDone : 1 );
    167             str = fmt.percentString( d );
    168 
    169             if( !haveUnverified && !leftUntilDone )
    170                 str = fmt.size(haveVerified) + ' (100%)';
    171             else if( !haveUnverified )
    172                 str = fmt.size(haveVerified) + ' of ' + fmt.size(sizeWhenDone) + ' (' + str +'%)';
    173             else
    174                 str = fmt.size(haveVerified) + ' of ' + fmt.size(sizeWhenDone) + ' (' + str +'%), ' + fmt.size(haveUnverified) + ' Unverified';
    175         }
    176         setTextContent(e.have_lb, str);
    177 
    178         //
    179         //  availability_lb
    180         //
    181 
    182         if(torrents.length < 1)
    183             str = none;
    184         else if( sizeWhenDone == 0 )
    185             str = none;
    186         else
    187             str = '' + fmt.percentString( ( 100.0 * available ) / sizeWhenDone ) +  '%';
    188         setTextContent(e.availability_lb, str);
    189 
    190         //
    191         //  downloaded_lb
    192         //
    193 
    194         if(torrents.length < 1)
    195             str = none;
    196         else {
    197             d = f = 0;
    198             for(i=0; t=torrents[i]; ++i) {
    199                 d += t.getDownloadedEver();
    200                 f += t.getFailedEver();
    201             }
    202             if(f)
    203                 str = fmt.size(d) + ' (' + fmt.size(f) + ' corrupt)';
    204             else
    205                 str = fmt.size(d);
    206         }
    207         setTextContent(e.downloaded_lb, str);
    208 
    209         //
    210         //  uploaded_lb
    211         //
    212 
    213         if(torrents.length < 1)
    214             str = none;
    215         else {
    216             d = u = 0;
    217             if(torrents.length == 1) {
    218                 d = torrents[0].getDownloadedEver();
    219                 u = torrents[0].getUploadedEver();
    220                                
    221                 if (d == 0)
    222                     d = torrents[0].getHaveValid();
    223             }
    224             else {
    225                 for(i=0; t=torrents[i]; ++i) {
    226                     d += t.getDownloadedEver();
    227                     u += t.getUploadedEver();
    228                 }
    229             }
    230             str = fmt.size(u) + ' (Ratio: ' + fmt.ratioString( Math.ratio(u,d))+')';
    231         }
    232         setTextContent(e.uploaded_lb, str);
    233 
    234         //
    235         // running time
    236         //
    237 
    238         if(torrents.length < 1)
    239             str = none;
    240         else {
    241             allPaused = true;
    242             baseline = torrents[0].getStartDate();
    243             for(i=0; t=torrents[i]; ++i) {
    244                 if(baseline != t.getStartDate())
    245                     baseline = 0;
    246                 if(!t.isStopped())
    247                     allPaused = false;
    248             }
    249             if(allPaused)
    250                 str = stateString; // paused || finished
    251             else if(!baseline)
    252                 str = mixed;
    253             else
    254                 str = fmt.timeInterval(now/1000 - baseline);
    255         }
    256         setTextContent(e.running_time_lb, str);
    257 
    258         //
    259         // remaining time
    260         //
    261 
    262         str = '';
    263         if(torrents.length < 1)
    264             str = none;
    265         else {
    266             baseline = torrents[0].getETA();
    267             for(i=0; t=torrents[i]; ++i) {
    268                 if(baseline != t.getETA()) {
    269                     str = mixed;
    270                     break;
    271                 }
    272             }
    273         }
    274         if(!str.length) {
    275             if(baseline < 0)
    276                 str = unknown;
    277             else
    278                 str = fmt.timeInterval(baseline);
    279         }
    280         setTextContent(e.remaining_time_lb, str);
    281 
    282         //
    283         // last activity
    284         //
    285 
    286         latest = -1;
    287         if(torrents.length < 1)
    288             str = none;
    289         else {
    290             baseline = torrents[0].getLastActivity();
    291             for(i=0; t=torrents[i]; ++i) {
    292                 d = t.getLastActivity();
    293                 if(latest < d)
    294                     latest = d;
    295             }
    296             d = now/1000 - latest; // seconds since last activity
    297             if(d < 0)
    298                 str = none;
    299             else if(d < 5)
    300                 str = 'Active now';
    301             else
    302                 str = fmt.timeInterval(d) + ' ago';
    303         }
    304         setTextContent(e.last_activity_lb, str);
    305 
    306         //
    307         // error
    308         //
    309 
    310         if(torrents.length < 1)
    311             str = none;
    312         else {
    313             str = torrents[0].getErrorString();
    314             for(i=0; t=torrents[i]; ++i) {
    315                 if(str != t.getErrorString()) {
    316                     str = mixed;
    317                     break;
    318                 }
    319             }
    320         }
    321         setTextContent(e.error_lb, str || none);
    322 
    323         //
    324         // size
    325         //
    326 
    327         if(torrents.length < 1)
    328             str = none;
    329         else {
    330             pieces = 0;
    331             size = 0;
    332             pieceSize = torrents[0].getPieceSize();
    333             for(i=0; t=torrents[i]; ++i) {
    334                 pieces += t.getPieceCount();
    335                 size += t.getTotalSize();
    336                 if(pieceSize != t.getPieceSize())
    337                     pieceSize = 0;
    338             }
    339             if(!size)
    340                 str = none;
    341             else if(pieceSize > 0)
    342                 str = fmt.size(size) + ' (' + pieces.toStringWithCommas() + ' pieces @ ' + fmt.mem(pieceSize) + ')';
    343             else
    344                 str = fmt.size(size) + ' (' + pieces.toStringWithCommas() + ' pieces)';
    345         }
    346         setTextContent(e.size_lb, str);
    347 
    348         //
    349         //  hash
    350         //
    351 
    352         if(torrents.length < 1)
    353             str = none;
    354         else {
    355             str = torrents[0].getHashString();
    356             for(i=0; t=torrents[i]; ++i) {
    357                 if(str != t.getHashString()) {
    358                     str = mixed;
    359                     break;
    360                 }
    361             }
    362         }
    363         setTextContent(e.hash_lb, str);
    364 
    365         //
    366         //  privacy
    367         //
    368 
    369         if(torrents.length < 1)
    370             str = none;
    371         else {
    372             baseline = torrents[0].getPrivateFlag();
    373             str = baseline ? 'Private to this tracker -- DHT and PEX disabled' : 'Public torrent';
    374             for(i=0; t=torrents[i]; ++i) {
    375                 if(baseline != t.getPrivateFlag()) {
    376                     str = mixed;
    377                     break;
    378                 }
    379             }
    380         }
    381         setTextContent(e.privacy_lb, str);
    382 
    383         //
    384         //  comment
    385         //
    386 
    387         if(torrents.length < 1)
    388             str = none;
    389         else {
    390             str = torrents[0].getComment();
    391             for(i=0; t=torrents[i]; ++i) {
    392                 if(str != t.getComment()) {
    393                     str = mixed;
    394                     break;
    395                 }
    396             }
    397         }
    398         if(!str)
    399             str = none; 
    400         uri = parseUri(str);
    401         if (uri.protocol == 'http' || uri.parseUri == 'https') {
    402             str = encodeURI(str);
    403             setInnerHTML(e.comment_lb, '<a href="' + str + '" target="_blank" >' + str + '</a>');
    404         }
    405         else
    406             setTextContent(e.comment_lb, str);
    407 
    408         //
    409         //  origin
    410         //
    411 
    412         if(torrents.length < 1)
    413             str = none;
    414         else {
    415             mixed_creator = false;
    416             mixed_date = false;
    417             creator = torrents[0].getCreator();
    418             date = torrents[0].getDateCreated();
    419             for(i=0; t=torrents[i]; ++i) {
    420                 if(creator != t.getCreator())
    421                     mixed_creator = true;
    422                 if(date != t.getDateCreated())
    423                     mixed_date = true;
    424             }
    425             var empty_creator = !creator || !creator.length,
    426                 empty_date = !date;
    427             if(mixed_creator || mixed_date)
    428                 str = mixed;
    429             else if(empty_creator && empty_date)
    430                 str = unknown;
    431             else if(empty_date && !empty_creator)
    432                 str = 'Created by ' + creator;
    433             else if(empty_creator && !empty_date)
    434                 str = 'Created on ' + (new Date(date*1000)).toDateString();
    435             else
    436                 str = 'Created by ' + creator + ' on ' + (new Date(date*1000)).toDateString();
    437         }
    438         setTextContent(e.origin_lb, str);
    439 
    440         //
    441         //  foldername
    442         //
    443 
    444         if(torrents.length < 1)
    445             str = none;
    446         else {
    447             str = torrents[0].getDownloadDir();
    448             for(i=0; t=torrents[i]; ++i) {
    449                 if(str != t.getDownloadDir()) {
    450                     str = mixed;
    451                     break;
    452                 }
    453             }
    454         }
    455         setTextContent(e.foldername_lb, str);
    456     },
    457 
    458     /****
    459     *****  FILES PAGE
    460     ****/
    461 
    462     changeFileCommand = function(fileIndices, command) {
    463         var torrentId = data.file_torrent.getId();
    464         data.controller.changeFileCommand(torrentId, fileIndices, command);
    465     },
    466 
    467     onFileWantedToggled = function(ev, fileIndices, want) {
    468         changeFileCommand(fileIndices, want?'files-wanted':'files-unwanted');
    469     },
    470 
    471     onFilePriorityToggled = function(ev, fileIndices, priority) {
    472         var command;
    473         switch(priority) {
    474             case -1: command = 'priority-low'; break;
    475             case  1: command = 'priority-high'; break;
    476             default: command = 'priority-normal'; break;
    477         }
    478         changeFileCommand(fileIndices, command);
    479     },
    480 
    481     onNameClicked = function(ev, fileRow, fileIndices) {
    482         $(fileRow.getElement()).siblings().slideToggle();
    483     },
    484 
    485     clearFileList = function() {
    486         $(data.elements.file_list).empty();
    487         delete data.file_torrent;
    488         delete data.file_torrent_n;
    489         delete data.file_rows;
    490     },
    491 
    492     createFileTreeModel = function (tor) {
    493         var i, j, n, name, tokens, walk, tree, token, sub,
    494             leaves = [ ],
    495             tree = { children: { }, file_indices: [ ] };
    496 
    497         n = tor.getFileCount();
    498         for (i=0; i<n; ++i) {
    499             name = tor.getFile(i).name;
    500             tokens = name.split('/');
    501             walk = tree;
    502             for (j=0; j<tokens.length; ++j) {
    503                 token = tokens[j];
    504                 sub = walk.children[token];
    505                 if (!sub) {
    506                     walk.children[token] = sub = {
    507                       name: token,
    508                       parent: walk,
    509                       children: { },
    510                       file_indices: [ ],
    511                       depth: j
    512                     };
    513                 }
    514                 walk = sub;
    515             }
    516             walk.file_index = i;
    517             delete walk.children;
    518             leaves.push (walk);
    519         }
    520 
    521         for (i=0; i<leaves.length; ++i) {
    522             walk = leaves[i];
    523             j = walk.file_index;
    524             do {
    525                 walk.file_indices.push (j);
    526                 walk = walk.parent;
    527             } while (walk);
    528         }
    529 
    530         return tree;
    531     },
    532 
    533     addNodeToView = function (tor, parent, sub, i) {
    534         var row;
    535         row = new FileRow(tor, sub.depth, sub.name, sub.file_indices, i%2);
    536         data.file_rows.push(row);
    537         parent.appendChild(row.getElement());
    538         $(row).bind('wantedToggled',onFileWantedToggled);
    539         $(row).bind('priorityToggled',onFilePriorityToggled);
    540         $(row).bind('nameClicked',onNameClicked);
    541     },
    542 
    543     addSubtreeToView = function (tor, parent, sub, i) {
    544         var key, div;
    545         div = document.createElement('div');
    546         if (sub.parent)
    547             addNodeToView (tor, div, sub, i++);
    548         if (sub.children)
    549             for (key in sub.children)
    550                 i = addSubtreeToView (tor, div, sub.children[key]); 
    551         parent.appendChild(div);
    552         return i;
    553     },
    554                
    555     updateFilesPage = function() {
    556         var i, n, tor, fragment, tree,
    557             file_list = data.elements.file_list,
    558             torrents = data.torrents;
    559 
    560         // only show one torrent at a time
    561         if (torrents.length !== 1) {
    562             clearFileList();
    563             return;
    564         }
    565 
    566         tor = torrents[0];
    567         n = tor ? tor.getFileCount() : 0;
    568         if (tor!=data.file_torrent || n!=data.file_torrent_n) {
    569             // rebuild the file list...
    570             clearFileList();
    571             data.file_torrent = tor;
    572             data.file_torrent_n = n;
    573             data.file_rows = [ ];
    574             fragment = document.createDocumentFragment();
    575             tree = createFileTreeModel (tor);
    576             addSubtreeToView (tor, fragment, tree, 0);
    577             file_list.appendChild (fragment);
    578         } else {
    579             // ...refresh the already-existing file list
    580             for (i=0, n=data.file_rows.length; i<n; ++i)
    581                 data.file_rows[i].refresh();
    582         }
    583     },
    584 
    585     /****
    586     *****  PEERS PAGE
    587     ****/
    588 
    589     updatePeersPage = function() {
    590         var i, k, tor, peers, peer, parity,
    591             html = [],
    592             fmt = Transmission.fmt,
    593             peers_list = data.elements.peers_list,
    594             torrents = data.torrents;
    595 
    596         for (k=0; tor=torrents[k]; ++k)
    597         {
    598             peers = tor.getPeers();
    599             html.push('<div class="inspector_group">');
    600             if (torrents.length > 1) {
    601                 html.push('<div class="inspector_torrent_label">', sanitizeText(tor.getName()), '</div>');
    602             }
    603             if (!peers || !peers.length) {
    604                 html.push('<br></div>'); // firefox won't paint the top border if the div is empty
    605                 continue;
    606             }
    607             html.push('<table class="peer_list">',
    608                    '<tr class="inspector_peer_entry even">',
    609                    '<th class="encryptedCol"></th>',
    610                    '<th class="upCol">Up</th>',
    611                    '<th class="downCol">Down</th>',
    612                    '<th class="percentCol">%</th>',
    613                    '<th class="statusCol">Status</th>',
    614                    '<th class="addressCol">Address</th>',
    615                    '<th class="clientCol">Client</th>',
    616                    '</tr>');
    617             for (i=0; peer=peers[i]; ++i) {
    618                 parity = (i%2) ? 'odd' : 'even';
    619                 html.push('<tr class="inspector_peer_entry ', parity, '">',
    620                        '<td>', (peer.isEncrypted ? '<div class="encrypted-peer-cell" title="Encrypted Connection">'
    621                                                  : '<div class="unencrypted-peer-cell">'), '</div>', '</td>',
    622                        '<td>', (peer.rateToPeer ? fmt.speedBps(peer.rateToPeer) : ''), '</td>',
    623                        '<td>', (peer.rateToClient ? fmt.speedBps(peer.rateToClient) : ''), '</td>',
    624                        '<td class="percentCol">', Math.floor(peer.progress*100), '%', '</td>',
    625                        '<td>', fmt.peerStatus(peer.flagStr), '</td>',
    626                        '<td>', sanitizeText(peer.address), '</td>',
    627                        '<td class="clientCol">', sanitizeText(peer.clientName), '</td>',
    628                        '</tr>');
    629             }
    630             html.push('</table></div>');
    631         }
    632 
    633         setInnerHTML(peers_list, html.join(''));
    634     },
    635 
    636     /****
    637     *****  TRACKERS PAGE
    638     ****/
    639 
    640     getAnnounceState = function(tracker) {
    641         var timeUntilAnnounce, s = '';
    642         switch (tracker.announceState) {
    643             case Torrent._TrackerActive:
    644                 s = 'Announce in progress';
    645                 break;
    646             case Torrent._TrackerWaiting:
    647                 timeUntilAnnounce = tracker.nextAnnounceTime - ((new Date()).getTime() / 1000);
    648                 if (timeUntilAnnounce < 0) {
    649                     timeUntilAnnounce = 0;
    650                 }
    651                 s = 'Next announce in ' + Transmission.fmt.timeInterval(timeUntilAnnounce);
    652                 break;
    653             case Torrent._TrackerQueued:
    654                 s = 'Announce is queued';
    655                 break;
    656             case Torrent._TrackerInactive:
    657                 s = tracker.isBackup ?
    658                     'Tracker will be used as a backup' :
    659                     'Announce not scheduled';
    660                 break;
    661             default:
    662                 s = 'unknown announce state: ' + tracker.announceState;
    663         }
    664         return s;
    665     },
    666 
    667     lastAnnounceStatus = function(tracker) {
    668 
    669         var lastAnnounceLabel = 'Last Announce',
    670             lastAnnounce = [ 'N/A' ],
    671         lastAnnounceTime;
    672 
    673         if (tracker.hasAnnounced) {
    674             lastAnnounceTime = Transmission.fmt.timestamp(tracker.lastAnnounceTime);
    675             if (tracker.lastAnnounceSucceeded) {
    676                 lastAnnounce = [ lastAnnounceTime, ' (got ',  Transmission.fmt.countString('peer','peers',tracker.lastAnnouncePeerCount), ')' ];
    677             } else {
    678                 lastAnnounceLabel = 'Announce error';
    679                 lastAnnounce = [ (tracker.lastAnnounceResult ? (tracker.lastAnnounceResult + ' - ') : ''), lastAnnounceTime ];
    680             }
    681         }
    682         return { 'label':lastAnnounceLabel, 'value':lastAnnounce.join('') };
    683     },
    684 
    685     lastScrapeStatus = function(tracker) {
    686 
    687         var lastScrapeLabel = 'Last Scrape',
    688             lastScrape = 'N/A',
    689         lastScrapeTime;
    690 
    691         if (tracker.hasScraped) {
    692             lastScrapeTime = Transmission.fmt.timestamp(tracker.lastScrapeTime);
    693             if (tracker.lastScrapeSucceeded) {
    694                 lastScrape = lastScrapeTime;
    695             } else {
    696                 lastScrapeLabel = 'Scrape error';
    697                 lastScrape = (tracker.lastScrapeResult ? tracker.lastScrapeResult + ' - ' : '') + lastScrapeTime;
    698             }
    699         }
    700         return {'label':lastScrapeLabel, 'value':lastScrape};
    701     },
    702 
    703     updateTrackersPage = function() {
    704         var i, j, tier, tracker, trackers, tor,
    705             html, parity, lastAnnounceStatusHash,
    706             announceState, lastScrapeStatusHash,
    707             na = 'N/A',
    708             trackers_list = data.elements.trackers_list,
    709             torrents = data.torrents;
    710 
    711         // By building up the HTML as as string, then have the browser
    712         // turn this into a DOM tree, this is a fast operation.
    713         html = [];
    714         for (i=0; tor=torrents[i]; ++i)
    715         {
    716             html.push ('<div class="inspector_group">');
    717 
    718             if (torrents.length > 1)
    719                 html.push('<div class="inspector_torrent_label">', tor.getName(), '</div>');
    720 
    721             tier = -1;
    722             trackers = tor.getTrackers();
    723             for (j=0; tracker=trackers[j]; ++j)
    724             {
    725                 if (tier != tracker.tier)
    726                 {
    727                     if (tier !== -1) // close previous tier
    728                         html.push('</ul></div>');
    729 
    730                     tier = tracker.tier;
    731 
    732                     html.push('<div class="inspector_group_label">',
    733                           'Tier ', tier+1, '</div>',
    734                           '<ul class="tier_list">');
    735                 }
    736 
    737                 // Display construction
    738                 lastAnnounceStatusHash = lastAnnounceStatus(tracker);
    739                 announceState = getAnnounceState(tracker);
    740                 lastScrapeStatusHash = lastScrapeStatus(tracker);
    741                 parity = (j%2) ? 'odd' : 'even';
    742                 html.push('<li class="inspector_tracker_entry ', parity, '"><div class="tracker_host" title="', sanitizeText(tracker.announce), '">',
    743                       sanitizeText(tracker.host || tracker.announce), '</div>',
    744                       '<div class="tracker_activity">',
    745                       '<div>', lastAnnounceStatusHash['label'], ': ', lastAnnounceStatusHash['value'], '</div>',
    746                       '<div>', announceState, '</div>',
    747                       '<div>', lastScrapeStatusHash['label'], ': ', lastScrapeStatusHash['value'], '</div>',
    748                       '</div><table class="tracker_stats">',
    749                       '<tr><th>Seeders:</th><td>', (tracker.seederCount > -1 ? tracker.seederCount : na), '</td></tr>',
    750                       '<tr><th>Leechers:</th><td>', (tracker.leecherCount > -1 ? tracker.leecherCount : na), '</td></tr>',
    751                       '<tr><th>Downloads:</th><td>', (tracker.downloadCount > -1 ? tracker.downloadCount : na), '</td></tr>',
    752                       '</table></li>');
    753             }
    754             if (tier !== -1) // close last tier
    755                     html.push('</ul></div>');
    756 
    757             html.push('</div>'); // inspector_group
    758         }
    759 
    760         setInnerHTML (trackers_list, html.join(''));
    761     },
    762 
    763     initialize = function (controller) {
    764 
    765         var ti = '#torrent_inspector_';
    766 
    767         data.controller = controller;
    768 
    769         $('.inspector-tab').click(onTabClicked);
    770 
    771         data.elements.info_page      = $('#inspector-page-info')[0];
    772         data.elements.files_page     = $('#inspector-page-files')[0];
    773         data.elements.peers_page     = $('#inspector-page-peers')[0];
    774         data.elements.trackers_page  = $('#inspector-page-trackers')[0];
    775 
    776         data.elements.file_list      = $('#inspector_file_list')[0];
    777         data.elements.peers_list     = $('#inspector_peers_list')[0];
    778         data.elements.trackers_list  = $('#inspector_trackers_list')[0];
    779 
    780         data.elements.have_lb           = $('#inspector-info-have')[0];
    781         data.elements.availability_lb   = $('#inspector-info-availability')[0];
    782         data.elements.downloaded_lb     = $('#inspector-info-downloaded')[0];
    783         data.elements.uploaded_lb       = $('#inspector-info-uploaded')[0];
    784         data.elements.state_lb          = $('#inspector-info-state')[0];
    785         data.elements.running_time_lb   = $('#inspector-info-running-time')[0];
    786         data.elements.remaining_time_lb = $('#inspector-info-remaining-time')[0];
    787         data.elements.last_activity_lb  = $('#inspector-info-last-activity')[0];
    788         data.elements.error_lb          = $('#inspector-info-error')[0];
    789         data.elements.size_lb           = $('#inspector-info-size')[0];
    790         data.elements.foldername_lb     = $('#inspector-info-location')[0];
    791         data.elements.hash_lb           = $('#inspector-info-hash')[0];
    792         data.elements.privacy_lb        = $('#inspector-info-privacy')[0];
    793         data.elements.origin_lb         = $('#inspector-info-origin')[0];
    794         data.elements.comment_lb        = $('#inspector-info-comment')[0];
    795         data.elements.name_lb           = $('#torrent_inspector_name')[0];
    796 
    797         // force initial 'N/A' updates on all the pages
    798         updateInspector();
    799         updateInfoPage();
    800         updatePeersPage();
    801         updateTrackersPage();
    802         updateFilesPage();
    803     };
    804 
    805     /****
    806     *****  PUBLIC FUNCTIONS
    807     ****/
    808 
    809     this.setTorrents = function (torrents) {
    810         var d = data;
    811 
    812         // update the inspector when a selected torrent's data changes.
    813         $(d.torrents).unbind('dataChanged.inspector');
    814         $(torrents).bind('dataChanged.inspector', $.proxy(updateInspector,this));
    815         d.torrents = torrents;
    816 
    817         // periodically ask for updates to the inspector's torrents
    818         clearInterval(d.refreshInterval);
    819         d.refreshInterval = setInterval($.proxy(refreshTorrents,this), 2000);
    820         refreshTorrents();
    821 
    822         // refresh the inspector's UI
    823         updateInspector();
    824     };
    825 
    826     initialize (controller);
     10        var data = {
     11                controller: null,
     12                elements: { },
     13                torrents: [ ]
     14        },
     15
     16        needsExtraInfo = function (torrents) {
     17                var i, id, tor;
     18
     19                for (i = 0; tor = torrents[i]; i++)
     20                        if (!tor.hasExtraInfo())
     21                                return true;
     22
     23                return false;
     24        },
     25
     26        refreshTorrents = function () {
     27                var fields,
     28                    ids = $.map(data.torrents.slice(0), function (t) {return t.getId();});
     29
     30                if (ids && ids.length)
     31                {
     32                        fields = ['id'].concat(Torrent.Fields.StatsExtra);
     33
     34                        if (needsExtraInfo(data.torrents))
     35                                $.merge(fields, Torrent.Fields.InfoExtra);
     36
     37                        data.controller.updateTorrents(ids, fields);
     38                }
     39        },
     40
     41        onTabClicked = function (ev) {
     42                var tab = ev.currentTarget;
     43
     44                if (isMobileDevice)
     45                        ev.stopPropagation();
     46
     47                // select this tab and deselect the others
     48                $(tab).addClass('selected').siblings().removeClass('selected');
     49
     50                // show this tab and hide the others
     51                $('#' + tab.id.replace('tab','page')).show().siblings('.inspector-page').hide();
     52
     53                updateInspector();
     54        },
     55
     56        updateInspector = function () {
     57                var e = data.elements,
     58                    torrents = data.torrents,
     59                    name;
     60
     61                // update the name, which is shown on all the pages
     62                if (!torrents || !torrents.length)
     63                        name = 'No Selection';
     64                else if(torrents.length === 1)
     65                        name = torrents[0].getName();
     66                else
     67                        name = '' + torrents.length+' Transfers Selected';
     68                setTextContent(e.name_lb, name || na);
     69
     70                // update the visible page
     71                if ($(e.info_page).is(':visible'))
     72                        updateInfoPage();
     73                else if ($(e.peers_page).is(':visible'))
     74                        updatePeersPage();
     75                else if ($(e.trackers_page).is(':visible'))
     76                        updateTrackersPage();
     77                else if ($(e.files_page).is(':visible'))
     78                        updateFilesPage();
     79        },
     80
     81        /****
     82        *****  GENERAL INFO PAGE
     83        ****/
     84
     85        updateInfoPage = function () {
     86                var torrents = data.torrents,
     87                    e = data.elements,
     88                    fmt = Transmission.fmt,
     89                    none = 'None',
     90                    mixed = 'Mixed',
     91                    unknown = 'Unknown',
     92                    isMixed, allPaused, allFinished,
     93                    str,
     94                    baseline, it, s, i, t,
     95                    sizeWhenDone = 0,
     96                    leftUntilDone = 0,
     97                    available = 0,
     98                    haveVerified = 0,
     99                    haveUnverified = 0,
     100                    verifiedPieces = 0,
     101                    stateString,
     102                    latest,
     103                    pieces,
     104                    size,
     105                    pieceSize,
     106                    creator, mixed_creator,
     107                    date, mixed_date,
     108                    v, u, f, d, pct,
     109                    uri,
     110                    now = Date.now();
     111
     112                //
     113                //  state_lb
     114                //
     115
     116                if(torrents.length <1)
     117                        str = none;
     118                else {
     119                        isMixed = false;
     120                        allPaused = true;
     121                        allFinished = true;
     122
     123                        baseline = torrents[0].getStatus();
     124                        for(i=0; t=torrents[i]; ++i) {
     125                                it = t.getStatus();
     126                                if(it != baseline)
     127                                        isMixed = true;
     128                                if(!t.isStopped())
     129                                        allPaused = allFinished = false;
     130                                if(!t.isFinished())
     131                                        allFinished = false;
     132                        }
     133                        if( isMixed )
     134                                str = mixed;
     135                        else if( allFinished )
     136                                str = 'Finished';
     137                        else if( allPaused )
     138                                str = 'Paused';
     139                        else
     140                                str = torrents[0].getStateString();
     141                }
     142                setTextContent(e.state_lb, str);
     143                stateString = str;
     144
     145                //
     146                //  have_lb
     147                //
     148
     149                if(torrents.length < 1)
     150                        str = none;
     151                else {
     152                        baseline = torrents[0].getStatus();
     153                        for(i=0; t=torrents[i]; ++i) {
     154                                if(!t.needsMetaData()) {
     155                                        haveUnverified += t.getHaveUnchecked();
     156                                        v = t.getHaveValid();
     157                                        haveVerified += v;
     158                                        if(t.getPieceSize())
     159                                                verifiedPieces += v / t.getPieceSize();
     160                                        sizeWhenDone += t.getSizeWhenDone();
     161                                        leftUntilDone += t.getLeftUntilDone();
     162                                        available += (t.getHave()) + t.getDesiredAvailable();
     163                                }
     164                        }
     165
     166                        d = 100.0 * ( sizeWhenDone ? ( sizeWhenDone - leftUntilDone ) / sizeWhenDone : 1 );
     167                        str = fmt.percentString( d );
     168
     169                        if( !haveUnverified && !leftUntilDone )
     170                                str = fmt.size(haveVerified) + ' (100%)';
     171                        else if( !haveUnverified )
     172                                str = fmt.size(haveVerified) + ' of ' + fmt.size(sizeWhenDone) + ' (' + str +'%)';
     173                        else
     174                                str = fmt.size(haveVerified) + ' of ' + fmt.size(sizeWhenDone) + ' (' + str +'%), ' + fmt.size(haveUnverified) + ' Unverified';
     175                }
     176                setTextContent(e.have_lb, str);
     177
     178                //
     179                //  availability_lb
     180                //
     181
     182                if(torrents.length < 1)
     183                        str = none;
     184                else if( sizeWhenDone == 0 )
     185                        str = none;
     186                else
     187                        str = '' + fmt.percentString( ( 100.0 * available ) / sizeWhenDone ) +  '%';
     188                setTextContent(e.availability_lb, str);
     189
     190                //
     191                //  downloaded_lb
     192                //
     193
     194                if(torrents.length < 1)
     195                        str = none;
     196                else {
     197                        d = f = 0;
     198                        for(i=0; t=torrents[i]; ++i) {
     199                                d += t.getDownloadedEver();
     200                                f += t.getFailedEver();
     201                        }
     202                        if(f)
     203                                str = fmt.size(d) + ' (' + fmt.size(f) + ' corrupt)';
     204                        else
     205                                str = fmt.size(d);
     206                }
     207                setTextContent(e.downloaded_lb, str);
     208
     209                //
     210                //  uploaded_lb
     211                //
     212
     213                if(torrents.length < 1)
     214                        str = none;
     215                else {
     216                        d = u = 0;
     217                        if(torrents.length == 1) {
     218                                d = torrents[0].getDownloadedEver();
     219                                u = torrents[0].getUploadedEver();
     220
     221                                if (d == 0)
     222                                        d = torrents[0].getHaveValid();
     223                        }
     224                        else {
     225                                for(i=0; t=torrents[i]; ++i) {
     226                                        d += t.getDownloadedEver();
     227                                        u += t.getUploadedEver();
     228                                }
     229                        }
     230                        str = fmt.size(u) + ' (Ratio: ' + fmt.ratioString( Math.ratio(u,d))+')';
     231                }
     232                setTextContent(e.uploaded_lb, str);
     233
     234                //
     235                // running time
     236                //
     237
     238                if(torrents.length < 1)
     239                        str = none;
     240                else {
     241                        allPaused = true;
     242                        baseline = torrents[0].getStartDate();
     243                        for(i=0; t=torrents[i]; ++i) {
     244                                if(baseline != t.getStartDate())
     245                                        baseline = 0;
     246                                if(!t.isStopped())
     247                                        allPaused = false;
     248                        }
     249                        if(allPaused)
     250                                str = stateString; // paused || finished
     251                        else if(!baseline)
     252                                str = mixed;
     253                        else
     254                                str = fmt.timeInterval(now/1000 - baseline);
     255                }
     256                setTextContent(e.running_time_lb, str);
     257
     258                //
     259                // remaining time
     260                //
     261
     262                str = '';
     263                if(torrents.length < 1)
     264                        str = none;
     265                else {
     266                        baseline = torrents[0].getETA();
     267                        for(i=0; t=torrents[i]; ++i) {
     268                                if(baseline != t.getETA()) {
     269                                        str = mixed;
     270                                        break;
     271                                }
     272                        }
     273                }
     274                if(!str.length) {
     275                        if(baseline < 0)
     276                                str = unknown;
     277                        else
     278                                str = fmt.timeInterval(baseline);
     279                }
     280                setTextContent(e.remaining_time_lb, str);
     281
     282                //
     283                // last activity
     284                //
     285
     286                latest = -1;
     287                if(torrents.length < 1)
     288                        str = none;
     289                else {
     290                        baseline = torrents[0].getLastActivity();
     291                        for(i=0; t=torrents[i]; ++i) {
     292                                d = t.getLastActivity();
     293                                if(latest < d)
     294                                        latest = d;
     295                        }
     296                        d = now/1000 - latest; // seconds since last activity
     297                        if(d < 0)
     298                                str = none;
     299                        else if(d < 5)
     300                                str = 'Active now';
     301                        else
     302                                str = fmt.timeInterval(d) + ' ago';
     303                }
     304                setTextContent(e.last_activity_lb, str);
     305
     306                //
     307                // error
     308                //
     309
     310                if(torrents.length < 1)
     311                        str = none;
     312                else {
     313                        str = torrents[0].getErrorString();
     314                        for(i=0; t=torrents[i]; ++i) {
     315                                if(str != t.getErrorString()) {
     316                                        str = mixed;
     317                                        break;
     318                                }
     319                        }
     320                }
     321                setTextContent(e.error_lb, str || none);
     322
     323                //
     324                // size
     325                //
     326
     327                if(torrents.length < 1)
     328                        str = none;
     329                else {
     330                        pieces = 0;
     331                        size = 0;
     332                        pieceSize = torrents[0].getPieceSize();
     333                        for(i=0; t=torrents[i]; ++i) {
     334                                pieces += t.getPieceCount();
     335                                size += t.getTotalSize();
     336                                if(pieceSize != t.getPieceSize())
     337                                        pieceSize = 0;
     338                        }
     339                        if(!size)
     340                                str = none;
     341                        else if(pieceSize > 0)
     342                                str = fmt.size(size) + ' (' + pieces.toStringWithCommas() + ' pieces @ ' + fmt.mem(pieceSize) + ')';
     343                        else
     344                                str = fmt.size(size) + ' (' + pieces.toStringWithCommas() + ' pieces)';
     345                }
     346                setTextContent(e.size_lb, str);
     347
     348                //
     349                //  hash
     350                //
     351
     352                if(torrents.length < 1)
     353                        str = none;
     354                else {
     355                        str = torrents[0].getHashString();
     356                        for(i=0; t=torrents[i]; ++i) {
     357                                if(str != t.getHashString()) {
     358                                        str = mixed;
     359                                        break;
     360                                }
     361                        }
     362                }
     363                setTextContent(e.hash_lb, str);
     364
     365                //
     366                //  privacy
     367                //
     368
     369                if(torrents.length < 1)
     370                        str = none;
     371                else {
     372                        baseline = torrents[0].getPrivateFlag();
     373                        str = baseline ? 'Private to this tracker -- DHT and PEX disabled' : 'Public torrent';
     374                        for(i=0; t=torrents[i]; ++i) {
     375                                if(baseline != t.getPrivateFlag()) {
     376                                        str = mixed;
     377                                        break;
     378                                }
     379                        }
     380                }
     381                setTextContent(e.privacy_lb, str);
     382
     383                //
     384                //  comment
     385                //
     386
     387                if(torrents.length < 1)
     388                        str = none;
     389                else {
     390                        str = torrents[0].getComment();
     391                        for(i=0; t=torrents[i]; ++i) {
     392                                if(str != t.getComment()) {
     393                                        str = mixed;
     394                                        break;
     395                                }
     396                        }
     397                }
     398                if(!str)
     399                        str = none;
     400                uri = parseUri(str);
     401                if (uri.protocol == 'http' || uri.parseUri == 'https') {
     402                        str = encodeURI(str);
     403                        setInnerHTML(e.comment_lb, '<a href="' + str + '" target="_blank" >' + str + '</a>');
     404                }
     405                else
     406                        setTextContent(e.comment_lb, str);
     407
     408                //
     409                //  origin
     410                //
     411
     412                if(torrents.length < 1)
     413                        str = none;
     414                else {
     415                        mixed_creator = false;
     416                        mixed_date = false;
     417                        creator = torrents[0].getCreator();
     418                        date = torrents[0].getDateCreated();
     419                        for(i=0; t=torrents[i]; ++i) {
     420                                if(creator != t.getCreator())
     421                                        mixed_creator = true;
     422                                if(date != t.getDateCreated())
     423                                        mixed_date = true;
     424                        }
     425                        var empty_creator = !creator || !creator.length,
     426                            empty_date = !date;
     427                        if(mixed_creator || mixed_date)
     428                                str = mixed;
     429                        else if(empty_creator && empty_date)
     430                                str = unknown;
     431                        else if(empty_date && !empty_creator)
     432                                str = 'Created by ' + creator;
     433                        else if(empty_creator && !empty_date)
     434                                str = 'Created on ' + (new Date(date*1000)).toDateString();
     435                        else
     436                                str = 'Created by ' + creator + ' on ' + (new Date(date*1000)).toDateString();
     437                }
     438                setTextContent(e.origin_lb, str);
     439
     440                //
     441                //  foldername
     442                //
     443
     444                if(torrents.length < 1)
     445                        str = none;
     446                else {
     447                        str = torrents[0].getDownloadDir();
     448                        for(i=0; t=torrents[i]; ++i) {
     449                                if(str != t.getDownloadDir()) {
     450                                        str = mixed;
     451                                        break;
     452                                }
     453                        }
     454                }
     455                setTextContent(e.foldername_lb, str);
     456        },
     457
     458        /****
     459        *****  FILES PAGE
     460        ****/
     461
     462        changeFileCommand = function(fileIndices, command) {
     463                var torrentId = data.file_torrent.getId();
     464                data.controller.changeFileCommand(torrentId, fileIndices, command);
     465        },
     466
     467        onFileWantedToggled = function(ev, fileIndices, want) {
     468                changeFileCommand(fileIndices, want?'files-wanted':'files-unwanted');
     469        },
     470
     471        onFilePriorityToggled = function(ev, fileIndices, priority) {
     472                var command;
     473                switch(priority) {
     474                        case -1: command = 'priority-low'; break;
     475                        case  1: command = 'priority-high'; break;
     476                        default: command = 'priority-normal'; break;
     477                }
     478                changeFileCommand(fileIndices, command);
     479        },
     480
     481        onNameClicked = function(ev, fileRow, fileIndices) {
     482                $(fileRow.getElement()).siblings().slideToggle();
     483        },
     484
     485        clearFileList = function() {
     486                $(data.elements.file_list).empty();
     487                delete data.file_torrent;
     488                delete data.file_torrent_n;
     489                delete data.file_rows;
     490        },
     491
     492        createFileTreeModel = function (tor) {
     493                var i, j, n, name, tokens, walk, tree, token, sub,
     494                    leaves = [ ],
     495                    tree = { children: { }, file_indices: [ ] };
     496
     497                n = tor.getFileCount();
     498                for (i=0; i<n; ++i) {
     499                        name = tor.getFile(i).name;
     500                        tokens = name.split('/');
     501                        walk = tree;
     502                        for (j=0; j<tokens.length; ++j) {
     503                                token = tokens[j];
     504                                sub = walk.children[token];
     505                                if (!sub) {
     506                                        walk.children[token] = sub = {
     507                                                name: token,
     508                                                parent: walk,
     509                                                children: { },
     510                                                file_indices: [ ],
     511                                                depth: j
     512                                        };
     513                                }
     514                                walk = sub;
     515                        }
     516                        walk.file_index = i;
     517                        delete walk.children;
     518                        leaves.push (walk);
     519                }
     520
     521                for (i=0; i<leaves.length; ++i) {
     522                        walk = leaves[i];
     523                        j = walk.file_index;
     524                        do {
     525                                walk.file_indices.push (j);
     526                                walk = walk.parent;
     527                        } while (walk);
     528                }
     529
     530                return tree;
     531        },
     532
     533        addNodeToView = function (tor, parent, sub, i) {
     534                var row;
     535                row = new FileRow(tor, sub.depth, sub.name, sub.file_indices, i%2);
     536                data.file_rows.push(row);
     537                parent.appendChild(row.getElement());
     538                $(row).bind('wantedToggled',onFileWantedToggled);
     539                $(row).bind('priorityToggled',onFilePriorityToggled);
     540                $(row).bind('nameClicked',onNameClicked);
     541        },
     542
     543        addSubtreeToView = function (tor, parent, sub, i) {
     544                var key, div;
     545                div = document.createElement('div');
     546                if (sub.parent)
     547                        addNodeToView (tor, div, sub, i++);
     548                if (sub.children)
     549                        for (key in sub.children)
     550                                i = addSubtreeToView (tor, div, sub.children[key]);
     551                parent.appendChild(div);
     552                return i;
     553        },
     554
     555        updateFilesPage = function() {
     556                var i, n, tor, fragment, tree,
     557                    file_list = data.elements.file_list,
     558                    torrents = data.torrents;
     559
     560                // only show one torrent at a time
     561                if (torrents.length !== 1) {
     562                        clearFileList();
     563                        return;
     564                }
     565
     566                tor = torrents[0];
     567                n = tor ? tor.getFileCount() : 0;
     568                if (tor!=data.file_torrent || n!=data.file_torrent_n) {
     569                        // rebuild the file list...
     570                        clearFileList();
     571                        data.file_torrent = tor;
     572                        data.file_torrent_n = n;
     573                        data.file_rows = [ ];
     574                        fragment = document.createDocumentFragment();
     575                        tree = createFileTreeModel (tor);
     576                        addSubtreeToView (tor, fragment, tree, 0);
     577                        file_list.appendChild (fragment);
     578                } else {
     579                        // ...refresh the already-existing file list
     580                        for (i=0, n=data.file_rows.length; i<n; ++i)
     581                                data.file_rows[i].refresh();
     582                }
     583        },
     584
     585        /****
     586        *****  PEERS PAGE
     587        ****/
     588
     589        updatePeersPage = function() {
     590                var i, k, tor, peers, peer, parity,
     591                    html = [],
     592                    fmt = Transmission.fmt,
     593                    peers_list = data.elements.peers_list,
     594                    torrents = data.torrents;
     595
     596                for (k=0; tor=torrents[k]; ++k)
     597                {
     598                        peers = tor.getPeers();
     599                        html.push('<div class="inspector_group">');
     600                        if (torrents.length > 1) {
     601                                html.push('<div class="inspector_torrent_label">', sanitizeText(tor.getName()), '</div>');
     602                        }
     603                        if (!peers || !peers.length) {
     604                                html.push('<br></div>'); // firefox won't paint the top border if the div is empty
     605                                continue;
     606                        }
     607                        html.push('<table class="peer_list">',
     608                                  '<tr class="inspector_peer_entry even">',
     609                                  '<th class="encryptedCol"></th>',
     610                                  '<th class="upCol">Up</th>',
     611                                  '<th class="downCol">Down</th>',
     612                                  '<th class="percentCol">%</th>',
     613                                  '<th class="statusCol">Status</th>',
     614                                  '<th class="addressCol">Address</th>',
     615                                  '<th class="clientCol">Client</th>',
     616                                  '</tr>');
     617                        for (i=0; peer=peers[i]; ++i) {
     618                                parity = (i%2) ? 'odd' : 'even';
     619                                html.push('<tr class="inspector_peer_entry ', parity, '">',
     620                                          '<td>', (peer.isEncrypted ? '<div class="encrypted-peer-cell" title="Encrypted Connection">'
     621                                                                    : '<div class="unencrypted-peer-cell">'), '</div>', '</td>',
     622                                          '<td>', (peer.rateToPeer ? fmt.speedBps(peer.rateToPeer) : ''), '</td>',
     623                                          '<td>', (peer.rateToClient ? fmt.speedBps(peer.rateToClient) : ''), '</td>',
     624                                          '<td class="percentCol">', Math.floor(peer.progress*100), '%', '</td>',
     625                                          '<td>', fmt.peerStatus(peer.flagStr), '</td>',
     626                                          '<td>', sanitizeText(peer.address), '</td>',
     627                                          '<td class="clientCol">', sanitizeText(peer.clientName), '</td>',
     628                                          '</tr>');
     629                        }
     630                        html.push('</table></div>');
     631                }
     632
     633                setInnerHTML(peers_list, html.join(''));
     634        },
     635
     636        /****
     637        *****  TRACKERS PAGE
     638        ****/
     639
     640        getAnnounceState = function(tracker) {
     641                var timeUntilAnnounce, s = '';
     642                switch (tracker.announceState) {
     643                        case Torrent._TrackerActive:
     644                                s = 'Announce in progress';
     645                                break;
     646                        case Torrent._TrackerWaiting:
     647                                timeUntilAnnounce = tracker.nextAnnounceTime - ((new Date()).getTime() / 1000);
     648                                if (timeUntilAnnounce < 0) {
     649                                    timeUntilAnnounce = 0;
     650                                }
     651                                s = 'Next announce in ' + Transmission.fmt.timeInterval(timeUntilAnnounce);
     652                                break;
     653                        case Torrent._TrackerQueued:
     654                                s = 'Announce is queued';
     655                                break;
     656                        case Torrent._TrackerInactive:
     657                                s = tracker.isBackup ?
     658                                    'Tracker will be used as a backup' :
     659                                    'Announce not scheduled';
     660                                break;
     661                        default:
     662                                s = 'unknown announce state: ' + tracker.announceState;
     663                }
     664                return s;
     665        },
     666
     667        lastAnnounceStatus = function(tracker) {
     668
     669                var lastAnnounceLabel = 'Last Announce',
     670                    lastAnnounce = [ 'N/A' ],
     671                lastAnnounceTime;
     672
     673                if (tracker.hasAnnounced) {
     674                        lastAnnounceTime = Transmission.fmt.timestamp(tracker.lastAnnounceTime);
     675                        if (tracker.lastAnnounceSucceeded) {
     676                                lastAnnounce = [ lastAnnounceTime, ' (got ',  Transmission.fmt.countString('peer','peers',tracker.lastAnnouncePeerCount), ')' ];
     677                        } else {
     678                                lastAnnounceLabel = 'Announce error';
     679                                lastAnnounce = [ (tracker.lastAnnounceResult ? (tracker.lastAnnounceResult + ' - ') : ''), lastAnnounceTime ];
     680                        }
     681                }
     682                return { 'label':lastAnnounceLabel, 'value':lastAnnounce.join('') };
     683        },
     684
     685        lastScrapeStatus = function(tracker) {
     686
     687                var lastScrapeLabel = 'Last Scrape',
     688                    lastScrape = 'N/A',
     689                lastScrapeTime;
     690
     691                if (tracker.hasScraped) {
     692                        lastScrapeTime = Transmission.fmt.timestamp(tracker.lastScrapeTime);
     693                        if (tracker.lastScrapeSucceeded) {
     694                                lastScrape = lastScrapeTime;
     695                        } else {
     696                                lastScrapeLabel = 'Scrape error';
     697                                lastScrape = (tracker.lastScrapeResult ? tracker.lastScrapeResult + ' - ' : '') + lastScrapeTime;
     698                        }
     699                }
     700                return {'label':lastScrapeLabel, 'value':lastScrape};
     701        },
     702
     703        updateTrackersPage = function() {
     704                var i, j, tier, tracker, trackers, tor,
     705                    html, parity, lastAnnounceStatusHash,
     706                    announceState, lastScrapeStatusHash,
     707                    na = 'N/A',
     708                    trackers_list = data.elements.trackers_list,
     709                    torrents = data.torrents;
     710
     711                // By building up the HTML as as string, then have the browser
     712                // turn this into a DOM tree, this is a fast operation.
     713                html = [];
     714                for (i=0; tor=torrents[i]; ++i)
     715                {
     716                        html.push ('<div class="inspector_group">');
     717
     718                        if (torrents.length > 1)
     719                                html.push('<div class="inspector_torrent_label">', tor.getName(), '</div>');
     720
     721                        tier = -1;
     722                        trackers = tor.getTrackers();
     723                        for (j=0; tracker=trackers[j]; ++j)
     724                        {
     725                                if (tier != tracker.tier)
     726                                {
     727                                        if (tier !== -1) // close previous tier
     728                                                html.push('</ul></div>');
     729
     730                                        tier = tracker.tier;
     731
     732                                        html.push('<div class="inspector_group_label">',
     733                                                  'Tier ', tier+1, '</div>',
     734                                                  '<ul class="tier_list">');
     735                                }
     736
     737                                // Display construction
     738                                lastAnnounceStatusHash = lastAnnounceStatus(tracker);
     739                                announceState = getAnnounceState(tracker);
     740                                lastScrapeStatusHash = lastScrapeStatus(tracker);
     741                                parity = (j%2) ? 'odd' : 'even';
     742                                html.push('<li class="inspector_tracker_entry ', parity, '"><div class="tracker_host" title="', sanitizeText(tracker.announce), '">',
     743                                          sanitizeText(tracker.host || tracker.announce), '</div>',
     744                                          '<div class="tracker_activity">',
     745                                          '<div>', lastAnnounceStatusHash['label'], ': ', lastAnnounceStatusHash['value'], '</div>',
     746                                          '<div>', announceState, '</div>',
     747                                          '<div>', lastScrapeStatusHash['label'], ': ', lastScrapeStatusHash['value'], '</div>',
     748                                          '</div><table class="tracker_stats">',
     749                                          '<tr><th>Seeders:</th><td>', (tracker.seederCount > -1 ? tracker.seederCount : na), '</td></tr>',
     750                                          '<tr><th>Leechers:</th><td>', (tracker.leecherCount > -1 ? tracker.leecherCount : na), '</td></tr>',
     751                                          '<tr><th>Downloads:</th><td>', (tracker.downloadCount > -1 ? tracker.downloadCount : na), '</td></tr>',
     752                                          '</table></li>');
     753                        }
     754                        if (tier !== -1) // close last tier
     755                                html.push('</ul></div>');
     756
     757                        html.push('</div>'); // inspector_group
     758                }
     759
     760                setInnerHTML (trackers_list, html.join(''));
     761        },
     762
     763        initialize = function (controller) {
     764
     765                var ti = '#torrent_inspector_';
     766
     767                data.controller = controller;
     768
     769                $('.inspector-tab').click(onTabClicked);
     770
     771                data.elements.info_page      = $('#inspector-page-info')[0];
     772                data.elements.files_page     = $('#inspector-page-files')[0];
     773                data.elements.peers_page     = $('#inspector-page-peers')[0];
     774                data.elements.trackers_page  = $('#inspector-page-trackers')[0];
     775
     776                data.elements.file_list      = $('#inspector_file_list')[0];
     777                data.elements.peers_list     = $('#inspector_peers_list')[0];
     778                data.elements.trackers_list  = $('#inspector_trackers_list')[0];
     779
     780                data.elements.have_lb           = $('#inspector-info-have')[0];
     781                data.elements.availability_lb   = $('#inspector-info-availability')[0];
     782                data.elements.downloaded_lb     = $('#inspector-info-downloaded')[0];
     783                data.elements.uploaded_lb       = $('#inspector-info-uploaded')[0];
     784                data.elements.state_lb          = $('#inspector-info-state')[0];
     785                data.elements.running_time_lb   = $('#inspector-info-running-time')[0];
     786                data.elements.remaining_time_lb = $('#inspector-info-remaining-time')[0];
     787                data.elements.last_activity_lb  = $('#inspector-info-last-activity')[0];
     788                data.elements.error_lb          = $('#inspector-info-error')[0];
     789                data.elements.size_lb           = $('#inspector-info-size')[0];
     790                data.elements.foldername_lb     = $('#inspector-info-location')[0];
     791                data.elements.hash_lb           = $('#inspector-info-hash')[0];
     792                data.elements.privacy_lb        = $('#inspector-info-privacy')[0];
     793                data.elements.origin_lb         = $('#inspector-info-origin')[0];
     794                data.elements.comment_lb        = $('#inspector-info-comment')[0];
     795                data.elements.name_lb           = $('#torrent_inspector_name')[0];
     796
     797                // force initial 'N/A' updates on all the pages
     798                updateInspector();
     799                updateInfoPage();
     800                updatePeersPage();
     801                updateTrackersPage();
     802                updateFilesPage();
     803        };
     804
     805        /****
     806        *****  PUBLIC FUNCTIONS
     807        ****/
     808
     809        this.setTorrents = function (torrents) {
     810                var d = data;
     811
     812                // update the inspector when a selected torrent's data changes.
     813                $(d.torrents).unbind('dataChanged.inspector');
     814                $(torrents).bind('dataChanged.inspector', $.proxy(updateInspector,this));
     815                d.torrents = torrents;
     816
     817                // periodically ask for updates to the inspector's torrents
     818                clearInterval(d.refreshInterval);
     819                d.refreshInterval = setInterval($.proxy(refreshTorrents,this), 2000);
     820                refreshTorrents();
     821
     822                // refresh the inspector's UI
     823                updateInspector();
     824        };
     825
     826        initialize (controller);
    827827};
  • trunk/web/javascript/notifications.js

    r13422 r14523  
    22
    33$(document).ready(function () {
    4   if (!window.webkitNotifications) {
    5     return;
    6   }
     4        if (!window.webkitNotifications) {
     5                return;
     6        }
    77
    8   var notificationsEnabled = (window.webkitNotifications.checkPermission() === 0),
    9       toggle = $('#toggle_notifications');
     8        var notificationsEnabled = (window.webkitNotifications.checkPermission() === 0),
     9            toggle = $('#toggle_notifications');
    1010
    11   toggle.show();
    12   updateMenuTitle();
    13   $(transmission).bind('downloadComplete seedingComplete', function (event, torrent) {
    14         if (notificationsEnabled) {
     11        toggle.show();
     12        updateMenuTitle();
     13        $(transmission).bind('downloadComplete seedingComplete', function (event, torrent) {
     14                if (notificationsEnabled) {
    1515                var title = (event.type == 'downloadComplete' ? 'Download' : 'Seeding') + ' complete',
    1616                        content = torrent.getName(),
    1717                        notification;
    18        
     18
    1919                notification = window.webkitNotifications.createNotification('style/transmission/images/logo.png', title, content);
    20                 notification.show(); 
     20                notification.show();
    2121                setTimeout(function () {
    2222                  notification.cancel();
    2323                }, 5000);
    2424        };
    25   });
     25        });
    2626
    27   function updateMenuTitle() {
    28     toggle.html((notificationsEnabled ? 'Disable' : 'Enable') + ' Notifications');
    29   }
     27        function updateMenuTitle() {
     28                toggle.html((notificationsEnabled ? 'Disable' : 'Enable') + ' Notifications');
     29        }
    3030
    31   Notifications.toggle = function () {
    32     if (window.webkitNotifications.checkPermission() !== 0) {
    33       window.webkitNotifications.requestPermission(function () {
    34         notificationsEnabled = (window.webkitNotifications.checkPermission() === 0);
    35         updateMenuTitle();
    36       });
    37     } else {
    38       notificationsEnabled = !notificationsEnabled;
    39       updateMenuTitle();
    40     }
    41   };
     31        Notifications.toggle = function () {
     32                if (window.webkitNotifications.checkPermission() !== 0) {
     33                        window.webkitNotifications.requestPermission(function () {
     34                                notificationsEnabled = (window.webkitNotifications.checkPermission() === 0);
     35                                updateMenuTitle();
     36                        });
     37                } else {
     38                        notificationsEnabled = !notificationsEnabled;
     39                        updateMenuTitle();
     40                }
     41        };
    4242});
  • trunk/web/javascript/prefs-dialog.js

    r13218 r14523  
    88function PrefsDialog(remote) {
    99
    10     var data = {
    11         dialog: null,
    12         remote: null,
    13         elements: { },
    14 
    15         // all the RPC session keys that we have gui controls for
    16         keys: [
    17             'alt-speed-down',
    18             'alt-speed-time-begin',
    19             'alt-speed-time-day',
    20             'alt-speed-time-enabled',
    21             'alt-speed-time-end',
    22             'alt-speed-up',
    23             'blocklist-enabled',
    24             'blocklist-size',
    25             'blocklist-url',
    26             'dht-enabled',
    27             'download-dir',
    28             'encryption',
    29             'idle-seeding-limit',
    30             'idle-seeding-limit-enabled',
    31             'lpd-enabled',
    32             'peer-limit-global',
    33             'peer-limit-per-torrent',
    34             'peer-port',
    35             'peer-port-random-on-start',
    36             'pex-enabled',
    37             'port-forwarding-enabled',
    38             'rename-partial-files',
    39             'seedRatioLimit',
    40             'seedRatioLimited',
    41             'speed-limit-down',
    42             'speed-limit-down-enabled',
    43             'speed-limit-up',
    44             'speed-limit-up-enabled',
    45             'start-added-torrents',
    46             'utp-enabled'
    47         ],
    48 
    49         // map of keys that are enabled only if a 'parent' key is enabled
    50         groups: {
    51             'alt-speed-time-enabled': ['alt-speed-time-begin',
    52                                        'alt-speed-time-day',
    53                                        'alt-speed-time-end' ],
    54             'blocklist-enabled': ['blocklist-url',
    55                                   'blocklist-update-button' ],
    56             'idle-seeding-limit-enabled': [ 'idle-seeding-limit' ],
    57             'seedRatioLimited': [ 'seedRatioLimit' ],
    58             'speed-limit-down-enabled': [ 'speed-limit-down' ],
    59             'speed-limit-up-enabled': [ 'speed-limit-up' ]
    60         }
    61     },
    62 
    63     initTimeDropDown = function(e)
    64     {
    65         var i, hour, mins, value, content;
    66 
    67         for (i=0; i<24*4; ++i) {
    68             hour = parseInt(i/4, 10);
    69             mins = ((i%4) * 15);
    70             value = i * 15;
    71             content = hour + ':' + (mins || '00');
    72             e.options[i] = new Option(content, value);
    73         }
    74     },
    75 
    76     onPortChecked = function(response)
    77     {
    78         var is_open = response['arguments']['port-is-open'],
    79             text = 'Port is <b>' + (is_open ? 'Open' : 'Closed') + '</b>',
    80             e = data.elements.root.find('#port-label');
    81         setInnerHTML(e[0],text);
    82     },
    83 
    84     setGroupEnabled = function(parent_key, enabled)
    85     {
    86         var i, key, keys, root;
    87 
    88         if (parent_key in data.groups)
    89         {
    90             root = data.elements.root,
    91             keys = data.groups[parent_key];
    92 
    93             for (i=0; key=keys[i]; ++i)
    94                 root.find('#'+key).attr('disabled',!enabled);
    95         }
    96     },
    97 
    98     onBlocklistUpdateClicked = function ()
    99     {
    100         data.remote.updateBlocklist();
    101         setBlocklistButtonEnabled(false);
    102     },
    103     setBlocklistButtonEnabled = function(b)
    104     {
    105         var e = data.elements.blocklist_button;
    106         e.attr('disabled',!b);
    107         e.val(b ? 'Update' : 'Updating...');
    108     },
    109 
    110     getValue = function(e)
    111     {
    112         var str;
    113 
    114         switch (e[0].type)
    115         {
    116             case 'checkbox':
    117             case 'radio':
    118                 return e.prop('checked');
    119 
    120             case 'text':
    121             case 'url':
    122             case 'email':
    123             case 'number':
    124             case 'search':
    125             case 'select-one':
    126                 str = e.val();
    127                 if( parseInt(str,10).toString() === str)
    128                     return parseInt(str,10);
    129                 if( parseFloat(str).toString() === str)
    130                     return parseFloat(str);
    131                 return str;
    132 
    133             default:
    134                 return null;
    135         }
    136     },
    137 
    138     /* this callback is for controls whose changes can be applied
    139        immediately, like checkboxs, radioboxes, and selects */
    140     onControlChanged = function(ev)
    141     {
    142         var o = {};
    143         o[ev.target.id] = getValue($(ev.target));
    144         data.remote.savePrefs(o);
    145     },
    146        
    147     /* these two callbacks are for controls whose changes can't be applied
    148        immediately -- like a text entry field -- because it takes many
    149        change events for the user to get to the desired result */
    150     onControlFocused  = function(ev)
    151     {
    152         data.oldValue = getValue($(ev.target));
    153     },
    154     onControlBlurred  = function(ev)
    155     {
    156         var newValue = getValue($(ev.target));
    157         if (newValue !== data.oldValue)
    158         {
    159             var o = {};
    160             o[ev.target.id] = newValue;
    161             data.remote.savePrefs(o);
    162             delete data.oldValue;
    163         }
    164     },
    165 
    166     getDefaultMobileOptions = function()
    167     {
    168         return {
    169             width: $(window).width(),
    170             height: $(window).height(),
    171             position: [ 'left', 'top' ]
    172         };
    173     },
    174 
    175     initialize = function (remote)
    176     {
    177         var i, key, e, o;
    178 
    179         data.remote = remote;
    180 
    181         e = $('#prefs-dialog');
    182         data.elements.root = e;
    183 
    184         initTimeDropDown(e.find('#alt-speed-time-begin')[0]);
    185         initTimeDropDown(e.find('#alt-speed-time-end')[0]);
    186 
    187         o = isMobileDevice
    188           ? getDefaultMobileOptions()
    189           : { width: 350, height: 400 };
    190         o.autoOpen = false;
    191         o.show = o.hide = 'fade';
    192         o.close = onDialogClosed;
    193         e.tabbedDialog(o);
    194 
    195         e = e.find('#blocklist-update-button');
    196         data.elements.blocklist_button = e;
    197         e.click(onBlocklistUpdateClicked);
    198 
    199         // listen for user input
    200         for (i=0; key=data.keys[i]; ++i)
    201         {
    202             e = data.elements.root.find('#'+key);
    203             switch (e[0].type)
    204             {
    205                 case 'checkbox':
    206                 case 'radio':
    207                 case 'select-one':
    208                     e.change(onControlChanged);
    209                     break;
    210 
    211                 case 'text':
    212                 case 'url':
    213                 case 'email':
    214                 case 'number':
    215                 case 'search':
    216                     e.focus(onControlFocused);
    217                     e.blur(onControlBlurred);
    218 
    219                 default:
    220                     break;
    221             }
    222         }
    223     },
    224 
    225     getValues = function()
    226     {
    227         var i, key, val, o={},
    228             keys = data.keys,
    229             root = data.elements.root;
    230 
    231         for (i=0; key=keys[i]; ++i) {
    232             val = getValue(root.find('#'+key));
    233             if (val !== null)
    234                 o[key] = val;
    235         }
    236 
    237         return o;
    238     },
    239 
    240     onDialogClosed = function()
    241     {
    242         transmission.hideMobileAddressbar();
    243 
    244         $(data.dialog).trigger('closed', getValues());
    245     };
    246 
    247     /****
    248     *****  PUBLIC FUNCTIONS
    249     ****/
    250 
    251     // update the dialog's controls
    252     this.set = function (o)
    253     {
    254         var e, i, key, val, option,
    255             keys = data.keys,
    256             root = data.elements.root;
    257 
    258         setBlocklistButtonEnabled(true);
    259 
    260         for (i=0; key=keys[i]; ++i)
    261         {
    262             val = o[key];
    263             e = root.find('#'+key);
    264 
    265             if (key === 'blocklist-size')
    266             {
    267                 // special case -- regular text area
    268                 e.text('' + val.toStringWithCommas());
    269             }
    270             else switch (e[0].type)
    271             {
    272                 case 'checkbox':
    273                 case 'radio':
    274                     e.prop('checked', val);
    275                     setGroupEnabled(key, val);
    276                     break;
    277                 case 'text':
    278                 case 'url':
    279                 case 'email':
    280                 case 'number':
    281                 case 'search':
    282                     // don't change the text if the user's editing it.
    283                     // it's very annoying when that happens!
    284                     if (e[0] !== document.activeElement)
    285                         e.val(val);
    286                     break;
    287                 case 'select-one':
    288                     e.val(val);
    289                     break;
    290                 default:
    291                     break;
    292             }
    293         }
    294     };
    295 
    296     this.show = function ()
    297     {
    298         transmission.hideMobileAddressbar();
    299 
    300         setBlocklistButtonEnabled(true);
    301         data.remote.checkPort(onPortChecked,this);
    302         data.elements.root.dialog('open');
    303     };
    304 
    305     this.close = function ()
    306     {
    307         transmission.hideMobileAddressbar();
    308         data.elements.root.dialog('close');
    309     },
    310 
    311     this.shouldAddedTorrentsStart = function()
    312     {
    313         return data.elements.root.find('#start-added-torrents')[0].checked;
    314     };
    315 
    316     data.dialog = this;
    317     initialize (remote);
     10        var data = {
     11            dialog: null,
     12            remote: null,
     13            elements: { },
     14
     15            // all the RPC session keys that we have gui controls for
     16            keys: [
     17                'alt-speed-down',
     18                'alt-speed-time-begin',
     19                'alt-speed-time-day',
     20                'alt-speed-time-enabled',
     21                'alt-speed-time-end',
     22                'alt-speed-up',
     23                'blocklist-enabled',
     24                'blocklist-size',
     25                'blocklist-url',
     26                'dht-enabled',
     27                'download-dir',
     28                'encryption',
     29                'idle-seeding-limit',
     30                'idle-seeding-limit-enabled',
     31                'lpd-enabled',
     32                'peer-limit-global',
     33                'peer-limit-per-torrent',
     34                'peer-port',
     35                'peer-port-random-on-start',
     36                'pex-enabled',
     37                'port-forwarding-enabled',
     38                'rename-partial-files',
     39                'seedRatioLimit',
     40                'seedRatioLimited',
     41                'speed-limit-down',
     42                'speed-limit-down-enabled',
     43                'speed-limit-up',
     44                'speed-limit-up-enabled',
     45                'start-added-torrents',
     46                'utp-enabled'
     47            ],
     48
     49            // map of keys that are enabled only if a 'parent' key is enabled
     50            groups: {
     51                'alt-speed-time-enabled': ['alt-speed-time-begin',
     52                                           'alt-speed-time-day',
     53                                           'alt-speed-time-end' ],
     54                'blocklist-enabled': ['blocklist-url',
     55                                      'blocklist-update-button' ],
     56                'idle-seeding-limit-enabled': [ 'idle-seeding-limit' ],
     57                'seedRatioLimited': [ 'seedRatioLimit' ],
     58                'speed-limit-down-enabled': [ 'speed-limit-down' ],
     59                'speed-limit-up-enabled': [ 'speed-limit-up' ]
     60            }
     61        },
     62
     63        initTimeDropDown = function(e)
     64        {
     65                var i, hour, mins, value, content;
     66
     67                for (i=0; i<24*4; ++i) {
     68                        hour = parseInt(i/4, 10);
     69                        mins = ((i%4) * 15);
     70                        value = i * 15;
     71                        content = hour + ':' + (mins || '00');
     72                        e.options[i] = new Option(content, value);
     73                }
     74        },
     75
     76        onPortChecked = function(response)
     77        {
     78                var is_open = response['arguments']['port-is-open'],
     79                    text = 'Port is <b>' + (is_open ? 'Open' : 'Closed') + '</b>',
     80                    e = data.elements.root.find('#port-label');
     81                setInnerHTML(e[0],text);
     82        },
     83
     84        setGroupEnabled = function(parent_key, enabled)
     85        {
     86                var i, key, keys, root;
     87
     88                if (parent_key in data.groups)
     89                {
     90                        root = data.elements.root,
     91                        keys = data.groups[parent_key];
     92
     93                        for (i=0; key=keys[i]; ++i)
     94                                root.find('#'+key).attr('disabled',!enabled);
     95                }
     96        },
     97
     98        onBlocklistUpdateClicked = function ()
     99        {
     100                data.remote.updateBlocklist();
     101                setBlocklistButtonEnabled(false);
     102        },
     103        setBlocklistButtonEnabled = function(b)
     104        {
     105                var e = data.elements.blocklist_button;
     106                e.attr('disabled',!b);
     107                e.val(b ? 'Update' : 'Updating...');
     108        },
     109
     110        getValue = function(e)
     111        {
     112                var str;
     113
     114                switch (e[0].type)
     115                {
     116                        case 'checkbox':
     117                        case 'radio':
     118                                return e.prop('checked');
     119
     120                        case 'text':
     121                        case 'url':
     122                        case 'email':
     123                        case 'number':
     124                        case 'search':
     125                        case 'select-one':
     126                                str = e.val();
     127                                if( parseInt(str,10).toString() === str)
     128                                        return parseInt(str,10);
     129                                if( parseFloat(str).toString() === str)
     130                                        return parseFloat(str);
     131                                return str;
     132
     133                        default:
     134                                return null;
     135                }
     136        },
     137
     138        /* this callback is for controls whose changes can be applied
     139           immediately, like checkboxs, radioboxes, and selects */
     140        onControlChanged = function(ev)
     141        {
     142                var o = {};
     143                o[ev.target.id] = getValue($(ev.target));
     144                data.remote.savePrefs(o);
     145        },
     146
     147        /* these two callbacks are for controls whose changes can't be applied
     148           immediately -- like a text entry field -- because it takes many
     149           change events for the user to get to the desired result */
     150        onControlFocused  = function(ev)
     151        {
     152                data.oldValue = getValue($(ev.target));
     153        },
     154        onControlBlurred  = function(ev)
     155        {
     156                var newValue = getValue($(ev.target));
     157                if (newValue !== data.oldValue)
     158                {
     159                        var o = {};
     160                        o[ev.target.id] = newValue;
     161                        data.remote.savePrefs(o);
     162                        delete data.oldValue;
     163                }
     164        },
     165
     166        getDefaultMobileOptions = function()
     167        {
     168                return {
     169                        width: $(window).width(),
     170                        height: $(window).height(),
     171                        position: [ 'left', 'top' ]
     172                };
     173        },
     174
     175        initialize = function (remote)
     176        {
     177                var i, key, e, o;
     178
     179                data.remote = remote;
     180
     181                e = $('#prefs-dialog');
     182                data.elements.root = e;
     183
     184                initTimeDropDown(e.find('#alt-speed-time-begin')[0]);
     185                initTimeDropDown(e.find('#alt-speed-time-end')[0]);
     186
     187                o = isMobileDevice
     188                  ? getDefaultMobileOptions()
     189                  : { width: 350, height: 400 };
     190                o.autoOpen = false;
     191                o.show = o.hide = 'fade';
     192                o.close = onDialogClosed;
     193                e.tabbedDialog(o);
     194
     195                e = e.find('#blocklist-update-button');
     196                data.elements.blocklist_button = e;
     197                e.click(onBlocklistUpdateClicked);
     198
     199                // listen for user input
     200                for (i=0; key=data.keys[i]; ++i)
     201                {
     202                        e = data.elements.root.find('#'+key);
     203                        switch (e[0].type)
     204                        {
     205                                case 'checkbox':
     206                                case 'radio':
     207                                case 'select-one':
     208                                        e.change(onControlChanged);
     209                                        break;
     210
     211                                case 'text':
     212                                case 'url':
     213                                case 'email':
     214                                case 'number':
     215                                case 'search':
     216                                        e.focus(onControlFocused);
     217                                        e.blur(onControlBlurred);
     218
     219                                default:
     220                                        break;
     221                        }
     222                }
     223        },
     224
     225        getValues = function()
     226        {
     227                var i, key, val, o={},
     228                    keys = data.keys,
     229                    root = data.elements.root;
     230
     231                for (i=0; key=keys[i]; ++i) {
     232                        val = getValue(root.find('#'+key));
     233                        if (val !== null)
     234                                o[key] = val;
     235                }
     236
     237                return o;
     238        },
     239
     240        onDialogClosed = function()
     241        {
     242                transmission.hideMobileAddressbar();
     243
     244                $(data.dialog).trigger('closed', getValues());
     245        };
     246
     247        /****
     248        *****  PUBLIC FUNCTIONS
     249        ****/
     250
     251        // update the dialog's controls
     252        this.set = function (o)
     253        {
     254                var e, i, key, val, option,
     255                    keys = data.keys,
     256                    root = data.elements.root;
     257
     258                setBlocklistButtonEnabled(true);
     259
     260                for (i=0; key=keys[i]; ++i)
     261                {
     262                        val = o[key];
     263                        e = root.find('#'+key);
     264
     265                        if (key === 'blocklist-size')
     266                        {
     267                                // special case -- regular text area
     268                                e.text('' + val.toStringWithCommas());
     269                        }
     270                        else switch (e[0].type)
     271                        {
     272                                case 'checkbox':
     273                                case 'radio':
     274                                        e.prop('checked', val);
     275                                        setGroupEnabled(key, val);
     276                                        break;
     277                                case 'text':
     278                                case 'url':
     279                                case 'email':
     280                                case 'number':
     281                                case 'search':
     282                                        // don't change the text if the user's editing it.
     283                                        // it's very annoying when that happens!
     284                                        if (e[0] !== document.activeElement)
     285                                                e.val(val);
     286                                        break;
     287                                case 'select-one':
     288                                        e.val(val);
     289                                        break;
     290                                default:
     291                                        break;
     292                        }
     293                }
     294        };
     295
     296        this.show = function ()
     297        {
     298                transmission.hideMobileAddressbar();
     299
     300                setBlocklistButtonEnabled(true);
     301                data.remote.checkPort(onPortChecked,this);
     302                data.elements.root.dialog('open');
     303        };
     304
     305        this.close = function ()
     306        {
     307                transmission.hideMobileAddressbar();
     308                data.elements.root.dialog('close');
     309        },
     310
     311        this.shouldAddedTorrentsStart = function()
     312        {
     313                return data.elements.root.find('#start-added-torrents')[0].checked;
     314        };
     315
     316        data.dialog = this;
     317        initialize (remote);
    318318};
  • trunk/web/javascript/remote.js

    r14511 r14523  
    5555
    5656                remote._error = request.responseText
    57                                         ? request.responseText.trim().replace(/(<([^>]+)>)/ig,"")
    58                                         : "";
     57                              ? request.responseText.trim().replace(/(<([^>]+)>)/ig,"")
     58                              : "";
    5959                if (!remote._error.length)
    6060                        remote._error = 'Server not responding';
     
    102102                this.sendRequest(o, callback, context, async);
    103103        },
    104        
     104
    105105        checkPort: function(callback, context, async) {
    106106                var o = { method: 'port-test' };
     
    109109
    110110        renameTorrent: function(torrentIds, oldpath, newname, callback, context) {
    111                 var o = { method: 'torrent-rename-path',
    112                           arguments: { 'ids': torrentIds,
    113                                        'path': oldpath,
    114                                        'name': newname }
     111                var o = {
     112                        method: 'torrent-rename-path',
     113                        arguments: {
     114                                'ids': torrentIds,
     115                                'path': oldpath,
     116                                'name': newname
     117                        }
    115118                };
    116119                this.sendRequest(o, callback, context);
     
    125128                var o = {
    126129                        method: 'torrent-get',
    127                                 'arguments': {
     130                        arguments: {
    128131                                'fields': fields
    129132                        }
     
    185188        moveTorrents: function(torrent_ids, new_location, callback, context) {
    186189                var remote = this;
    187                 this.sendTorrentSetRequests( 'torrent-set-location', torrent_ids, 
     190                this.sendTorrentSetRequests( 'torrent-set-location', torrent_ids,
    188191                        {"move": true, "location": new_location}, callback, context);
    189192        },
  • trunk/web/javascript/torrent-row.js

    r14236 r14523  
    240240                        // append UL stats: ', uploaded 8.59 GiB (Ratio: 12.3)'
    241241                        c.push(', uploaded ',
    242                                 Transmission.fmt.size(t.getUploadedEver()),
    243                                 ' (Ratio ',
    244                                 Transmission.fmt.ratioString(t.getUploadRatio()),
    245                                 ')');
     242                               Transmission.fmt.size(t.getUploadedEver()),
     243                               ' (Ratio ',
     244                               Transmission.fmt.ratioString(t.getUploadRatio()),
     245                               ')');
    246246                } else { // not done yet
    247247                        c = [ Transmission.fmt.size(sizeWhenDone - t.getLeftUntilDone()),
     
    258258                        else
    259259                                c.push(Transmission.fmt.timeInterval(t.getETA()),
    260                                         ' remaining');
     260                                       ' remaining');
    261261                }
    262262
  • trunk/web/javascript/torrent.js

    r13551 r14523  
    132132        {
    133133                var i, observer;
    134                
     134
    135135                if (o[name] === value)
    136136                        return false;
     
    421421        return (a - b) || Torrent.compareByRatio(ta, tb);
    422422};
    423 
    424423Torrent.compareBySize = function(ta, tb)
    425424{
    426     var a = ta.getTotalSize(),
    427         b = tb.getTotalSize();
    428 
    429     return (a - b) || Torrent.compareByName(ta, tb);
    430 }
     425        var a = ta.getTotalSize(),
     426            b = tb.getTotalSize();
     427
     428        return (a - b) || Torrent.compareByName(ta, tb);
     429};
    431430
    432431Torrent.compareTorrents = function(a, b, sortMethod, sortDirection)
     
    448447                        i = Torrent.compareByProgress(a,b);
    449448                        break;
    450         case Prefs._SortBySize:
    451             i = Torrent.compareBySize(a,b);
    452             break;
     449                case Prefs._SortBySize:
     450                        i = Torrent.compareBySize(a,b);
     451                        break;
    453452                case Prefs._SortByState:
    454453                        i = Torrent.compareByState(a,b);
     
    489488                        torrents.sort(this.compareByProgress);
    490489                        break;
    491         case Prefs._SortBySize:
    492             torrents.sort(this.compareBySize);
    493             break;
     490                case Prefs._SortBySize:
     491                        torrents.sort(this.compareBySize);
     492                        break;
    494493                case Prefs._SortByState:
    495494                        torrents.sort(this.compareByState);
  • trunk/web/javascript/transmission.js

    r14518 r14523  
    9191                if (this.isMenuEnabled)
    9292                        this.createSettingsMenu();
    93  
     93
    9494                e = {};
    9595                e.torrent_list              = $('#torrent_list')[0];
     
    607607                delete this.sessionInterval;
    608608                if (enabled) {
    609                         var callback = $.proxy(this.loadDaemonPrefs,this),
     609                        var callback = $.proxy(this.loadDaemonPrefs,this),
    610610                            msec = 8000;
    611611                        this.sessionInterval = setInterval(callback, msec);
     
    705705                        case 'tipjar':
    706706                                window.open('http://www.transmissionbt.com/donate.php');
    707                                 break; 
     707                                break;
    708708
    709709                        case 'unlimited_download_rate':
     
    945945                        jQuery.each (fileInput[0].files, function(i,file) {
    946946                                var reader = new FileReader();
    947                                 reader.onload = function(e) { 
     947                                reader.onload = function(e) {
    948948                                        var contents = e.target.result;
    949949                                        var key = "base64,"
     
    970970                        var url = $('#torrent_upload_url').val();
    971971                        if (url != '') {
    972                                 if (url.match(/^[0-9a-f]{40}$/i)) 
     972                                if (url.match(/^[0-9a-f]{40}$/i))
    973973                                        url = 'magnet:?xt=urn:btih:'+url;
    974974                                var o = {
     
    10021002                        var ids = this.getTorrentIds(torrents);
    10031003                        this.remote.moveTorrents(
    1004                                 ids, 
    1005                                 $("input#torrent_path").val(), 
    1006                                 this.refreshTorrents, 
     1004                                ids,
     1005                                $("input#torrent_path").val(),
     1006                                this.refreshTorrents,
    10071007                                this);
    10081008                        $('#move_container').hide();
     
    12001200        {
    12011201                var limit, limited, e, b, text,
    1202                     fmt = Transmission.fmt,
    1203                     menu = $('#footer_super_menu');
     1202                    fmt = Transmission.fmt,
     1203                    menu = $('#footer_super_menu');
    12041204
    12051205                this.serverVersion = o.version;
     
    12281228
    12291229                        e = menu.find('#limited_download_rate');
    1230                         e.html('Limit (' + fmt.speed(limit) + ')');
    1231 
    1232                         if (!limited)
    1233                                 e = menu.find('#unlimited_download_rate');
     1230                        e.html('Limit (' + fmt.speed(limit) + ')');
     1231
     1232                        if (!limited)
     1233                                e = menu.find('#unlimited_download_rate');
    12341234                        e.selectMenuItem();
    12351235                }
     
    12421242
    12431243                        e = menu.find('#limited_upload_rate');
    1244                         e.html('Limit (' + fmt.speed(limit) + ')');
    1245 
    1246                         if (!limited)
    1247                                 e = menu.find('#unlimited_upload_rate');
     1244                        e.html('Limit (' + fmt.speed(limit) + ')');
     1245
     1246                        if (!limited)
     1247                                e = menu.find('#unlimited_upload_rate');
    12481248                        e.selectMenuItem();
    12491249                }
     
    15361536                for (i=0; row=rows[i]; ++i)
    15371537                        e.push(row.getElement());
    1538                 $(e).filter(":odd").addClass('even'); 
    1539                 $(e).filter(":even").removeClass('even'); 
     1538                $(e).filter(":odd").addClass('even');
     1539                $(e).filter(":even").removeClass('even');
    15401540
    15411541                // sync gui
     
    16811681                if (enabled) {
    16821682                        var callback = $.proxy(this.loadDaemonStats,this),
    1683                             msec = 5000;
     1683                            msec = 5000;
    16841684                        this.statsInterval = setInterval(callback, msec);
    16851685                }
Note: See TracChangeset for help on using the changeset viewer.