From d849a4ab4813cd9f0578e38638fdb977441f039a Mon Sep 17 00:00:00 2001
From: Keivan Beigi <me@keivan.io>
Date: Fri, 19 Dec 2014 17:21:03 -0800
Subject: [PATCH] New: Show source/seed info in manual search

---
 src/NzbDrone.Api/Indexers/ReleaseModule.cs    | 17 +++-
 src/NzbDrone.Api/Indexers/ReleaseResource.cs  |  5 +
 .../BitMeTvTests/BitMeTVFixture.cs            |  2 +-
 .../BroadcastheNetFixture.cs                  |  2 +-
 .../IndexerTests/EztvTests/EztvFixture.cs     |  2 +-
 .../IPTorrentsTests/IPTorrentsFixture.cs      |  2 +-
 .../KickassTorrentsFixture.cs                 |  2 +-
 .../IndexerTests/NyaaTests/NyaaFixture.cs     |  2 +-
 .../TorrentleechTests/TorrentleechFixture.cs  |  2 +-
 .../Search/TorrentSeedingSpecification.cs     |  6 +-
 .../BroadcastheNet/BroadcastheNetParser.cs    |  2 +-
 .../Indexers/TorrentRssParser.cs              |  2 +-
 src/NzbDrone.Core/Parser/Model/TorrentInfo.cs |  4 +-
 src/UI/Content/theme.less                     | 94 +++++++++++--------
 src/UI/Episode/Search/ManualLayout.js         | 15 ++-
 src/UI/Release/PeersCell.js                   | 34 +++++++
 src/UI/Release/ProtocolCell.js                | 28 ++++++
 src/UI/Release/ReleaseCollection.js           |  4 +-
 18 files changed, 169 insertions(+), 56 deletions(-)
 create mode 100644 src/UI/Release/PeersCell.js
 create mode 100644 src/UI/Release/ProtocolCell.js

diff --git a/src/NzbDrone.Api/Indexers/ReleaseModule.cs b/src/NzbDrone.Api/Indexers/ReleaseModule.cs
index bc2f18038..e151cfc7f 100644
--- a/src/NzbDrone.Api/Indexers/ReleaseModule.cs
+++ b/src/NzbDrone.Api/Indexers/ReleaseModule.cs
@@ -54,7 +54,7 @@ namespace NzbDrone.Api.Indexers
         private Response DownloadRelease(ReleaseResource release)
         {
             var remoteEpisode = _remoteEpisodeCache.Find(release.Guid);
-            
+
             if (remoteEpisode == null)
             {
                 _logger.Debug("Couldn't find requested release in cache, cache timeout probably expired.");
@@ -142,6 +142,21 @@ namespace NzbDrone.Api.Indexers
                 release.QualityWeight += release.Quality.Revision.Real * 10;
                 release.QualityWeight += release.Quality.Revision.Version;
 
+
+                var torrentRelease = downloadDecision.RemoteEpisode.Release as TorrentInfo;
+
+                if (torrentRelease != null)
+                {
+                    release.Protocol = DownloadProtocol.Torrent;
+                    release.Seeders = torrentRelease.Seeders;
+                    //TODO: move this up the chains
+                    release.Leechers = torrentRelease.Peers - torrentRelease.Seeders;
+                }
+                else
+                {
+                    release.Protocol = DownloadProtocol.Usenet;
+                }
+
                 result.Add(release);
             }
 
diff --git a/src/NzbDrone.Api/Indexers/ReleaseResource.cs b/src/NzbDrone.Api/Indexers/ReleaseResource.cs
index 3086113c5..f6490f188 100644
--- a/src/NzbDrone.Api/Indexers/ReleaseResource.cs
+++ b/src/NzbDrone.Api/Indexers/ReleaseResource.cs
@@ -42,6 +42,11 @@ namespace NzbDrone.Api.Indexers
         public DownloadProtocol DownloadProtocol { get; set; }
         public Int32 ReleaseWeight { get; set; }
 
+
+        public int? Seeders { get; set; }
+        public int? Leechers { get; set; }
+        public DownloadProtocol Protocol { get; set; }
+
         public Boolean IsDaily { get; set; }
         public Boolean IsAbsoluteNumbering { get; set; }
         public Boolean IsPossibleSpecialEpisode { get; set; }
diff --git a/src/NzbDrone.Core.Test/IndexerTests/BitMeTvTests/BitMeTVFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/BitMeTvTests/BitMeTVFixture.cs
index a6b1b6278..9ec01f30a 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/BitMeTvTests/BitMeTVFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/BitMeTvTests/BitMeTVFixture.cs
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.IndexerTests.BitMeTvTests
             torrentInfo.InfoHash.Should().Be(null);
             torrentInfo.MagnetUrl.Should().Be(null);
             torrentInfo.Peers.Should().Be(null);
-            torrentInfo.Seeds.Should().Be(null);
+            torrentInfo.Seeders.Should().Be(null);
         }
     }
 }
diff --git a/src/NzbDrone.Core.Test/IndexerTests/BroadcastheNetTests/BroadcastheNetFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/BroadcastheNetTests/BroadcastheNetFixture.cs
index fc1ec0af5..fefd10da5 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/BroadcastheNetTests/BroadcastheNetFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/BroadcastheNetTests/BroadcastheNetFixture.cs
@@ -54,7 +54,7 @@ namespace NzbDrone.Core.Test.IndexerTests.BroadcastheNetTests
             torrentInfo.TvRageId.Should().Be(4055);
             torrentInfo.MagnetUrl.Should().BeNullOrEmpty();
             torrentInfo.Peers.Should().Be(9);
-            torrentInfo.Seeds.Should().Be(40);
+            torrentInfo.Seeders.Should().Be(40);
         }
 
         private void VerifyBackOff()
diff --git a/src/NzbDrone.Core.Test/IndexerTests/EztvTests/EztvFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/EztvTests/EztvFixture.cs
index af2e680df..ac350320f 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/EztvTests/EztvFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/EztvTests/EztvFixture.cs
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.EztvTests
             torrentInfo.InfoHash.Should().Be("20FC4FBFA88272274AC671F857CC15144E9AA83E");
             torrentInfo.MagnetUrl.Should().Be("magnet:?xt=urn:btih:ED6E7P5IQJZCOSWGOH4FPTAVCRHJVKB6&dn=S4C.I.Grombil.Cyfandir.Pell.American.Interior.PDTV.x264-MVGroup");
             torrentInfo.Peers.Should().NotHaveValue();
-            torrentInfo.Seeds.Should().NotHaveValue();
+            torrentInfo.Seeders.Should().NotHaveValue();
         }
     }
 }
diff --git a/src/NzbDrone.Core.Test/IndexerTests/IPTorrentsTests/IPTorrentsFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/IPTorrentsTests/IPTorrentsFixture.cs
index 3fd02c0fd..4cf4bda66 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/IPTorrentsTests/IPTorrentsFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/IPTorrentsTests/IPTorrentsFixture.cs
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.IPTorrentsTests
             torrentInfo.InfoHash.Should().Be(null);
             torrentInfo.MagnetUrl.Should().Be(null);
             torrentInfo.Peers.Should().Be(null);
-            torrentInfo.Seeds.Should().Be(null);
+            torrentInfo.Seeders.Should().Be(null);
         }
     }
 }
diff --git a/src/NzbDrone.Core.Test/IndexerTests/KickassTorrentsTests/KickassTorrentsFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/KickassTorrentsTests/KickassTorrentsFixture.cs
index 728f75dd1..4fa34b012 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/KickassTorrentsTests/KickassTorrentsFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/KickassTorrentsTests/KickassTorrentsFixture.cs
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.IndexerTests.KickassTorrentsTests
             torrentInfo.InfoHash.Should().Be("208C4F7866612CC88BFEBC7C496FA72C2368D1C0");
             torrentInfo.MagnetUrl.Should().Be("magnet:?xt=urn:btih:208C4F7866612CC88BFEBC7C496FA72C2368D1C0&dn=doctor+stranger+e03+140512+hdtv+h264+720p+ipop+avi+ctrg&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce");
             torrentInfo.Peers.Should().Be(311);
-            torrentInfo.Seeds.Should().Be(206);
+            torrentInfo.Seeders.Should().Be(206);
         }
 
         [Test]
diff --git a/src/NzbDrone.Core.Test/IndexerTests/NyaaTests/NyaaFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NyaaTests/NyaaFixture.cs
index 6d48a6b42..e2f4f3b14 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/NyaaTests/NyaaFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/NyaaTests/NyaaFixture.cs
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NyaaTests
             torrentInfo.InfoHash.Should().Be(null);
             torrentInfo.MagnetUrl.Should().Be(null);
             torrentInfo.Peers.Should().Be(2);
-            torrentInfo.Seeds.Should().Be(1);
+            torrentInfo.Seeders.Should().Be(1);
         }
     }
 }
diff --git a/src/NzbDrone.Core.Test/IndexerTests/TorrentleechTests/TorrentleechFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/TorrentleechTests/TorrentleechFixture.cs
index cb9d2bc02..6c1a44d07 100644
--- a/src/NzbDrone.Core.Test/IndexerTests/TorrentleechTests/TorrentleechFixture.cs
+++ b/src/NzbDrone.Core.Test/IndexerTests/TorrentleechTests/TorrentleechFixture.cs
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentleechTests
             torrentInfo.InfoHash.Should().Be(null);
             torrentInfo.MagnetUrl.Should().Be(null);
             torrentInfo.Peers.Should().Be(7);
-            torrentInfo.Seeds.Should().Be(1);
+            torrentInfo.Seeders.Should().Be(1);
         }
     }
 }
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs
index 12ac5e7ab..ab8571cdb 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs
@@ -31,10 +31,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
                 return Decision.Accept();
             }
 
-            if (torrentInfo.Seeds != null && torrentInfo.Seeds < 1)
+            if (torrentInfo.Seeders != null && torrentInfo.Seeders < 1)
             {
-                _logger.Debug("Not enough seeders. ({0})", torrentInfo.Seeds);
-                return Decision.Reject("Not enough seeders. ({0})", torrentInfo.Seeds);
+                _logger.Debug("Not enough seeders. ({0})", torrentInfo.Seeders);
+                return Decision.Reject("Not enough seeders. ({0})", torrentInfo.Seeders);
             }
 
             return Decision.Accept();
diff --git a/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetParser.cs b/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetParser.cs
index 6e1b94bdf..96ab86226 100644
--- a/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetParser.cs
+++ b/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetParser.cs
@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
                 torrentInfo.PublishDate = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).ToUniversalTime().AddSeconds(torrent.Time);
                 //torrentInfo.MagnetUrl = 
                 torrentInfo.InfoHash = torrent.InfoHash;
-                torrentInfo.Seeds = torrent.Seeders;
+                torrentInfo.Seeders = torrent.Seeders;
                 torrentInfo.Peers = torrent.Leechers;
 
                 results.Add(torrentInfo);
diff --git a/src/NzbDrone.Core/Indexers/TorrentRssParser.cs b/src/NzbDrone.Core/Indexers/TorrentRssParser.cs
index 14ea6eea3..7de611fa0 100644
--- a/src/NzbDrone.Core/Indexers/TorrentRssParser.cs
+++ b/src/NzbDrone.Core/Indexers/TorrentRssParser.cs
@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Indexers
 
             result.InfoHash = GetInfoHash(item);
             result.MagnetUrl = GetMagnetUrl(item);
-            result.Seeds = GetSeeders(item);
+            result.Seeders = GetSeeders(item);
             result.Peers = GetPeers(item);
 
             return result;
diff --git a/src/NzbDrone.Core/Parser/Model/TorrentInfo.cs b/src/NzbDrone.Core/Parser/Model/TorrentInfo.cs
index 889a1acad..e8c1c79cf 100644
--- a/src/NzbDrone.Core/Parser/Model/TorrentInfo.cs
+++ b/src/NzbDrone.Core/Parser/Model/TorrentInfo.cs
@@ -6,7 +6,7 @@ namespace NzbDrone.Core.Parser.Model
     {
         public string MagnetUrl { get; set; }
         public string InfoHash { get; set; }
-        public Int32? Seeds { get; set; }
+        public Int32? Seeders { get; set; }
         public Int32? Peers { get; set; }
 
         public static Int32? GetSeeders(ReleaseInfo release)
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Parser.Model
             {
                 return null;
             }
-            return torrentInfo.Seeds;
+            return torrentInfo.Seeders;
         }
     }
 }
\ No newline at end of file
diff --git a/src/UI/Content/theme.less b/src/UI/Content/theme.less
index a3b03dbd4..16a30ac70 100644
--- a/src/UI/Content/theme.less
+++ b/src/UI/Content/theme.less
@@ -19,7 +19,7 @@
 @import "../Hotkeys/hotkeys";
 
 .main-region {
-  @media (min-width: @screen-lg-min) {
+  @media (min-width : @screen-lg-min) {
     padding-left  : 30px;
     padding-right : 30px;
   }
@@ -28,12 +28,12 @@
 .toolbar {
 
   &:after {
-    visibility: hidden;
-    display: block;
-    font-size: 0;
-    content: " ";
-    clear: both;
-    height: 0;
+    visibility : hidden;
+    display    : block;
+    font-size  : 0;
+    content    : " ";
+    clear      : both;
+    height     : 0;
   }
 
   .page-toolbar {
@@ -41,13 +41,13 @@
     margin-bottom : 30px;
 
     .toolbar-group {
-      display: inline-block;
+      display : inline-block;
     }
 
     .sorting-buttons {
       .sorting-title {
-        display: inline-block;
-        width: 110px;
+        display : inline-block;
+        width   : 110px;
       }
     }
   }
@@ -86,7 +86,7 @@
 
 .control-panel-visible {
   #scroll-up {
-    bottom: 100px;
+    bottom : 100px;
   }
 }
 
@@ -101,7 +101,7 @@
 }
 
 .label-disabled {
-  opacity: 0.5;
+  opacity : 0.5;
 }
 
 th {
@@ -123,10 +123,9 @@ a, .btn {
 }
 
 body {
-  background:
-    url('../Content/Images/background/logo.png') 50px center no-repeat fixed,#272727;
+  background    : url('../Content/Images/background/logo.png') 50px center no-repeat fixed, #272727;
 
-  margin-bottom    : 100px;
+  margin-bottom : 100px;
   p {
     font-size : 0.9em;
   }
@@ -163,10 +162,10 @@ body {
   .card(#aaaaaa);
   /* width      : 1210px;
   min-width  : 1210px; */
-  max-width  : 1210px;
-  margin     : auto;
-//  margin-top : -70px;
-  padding    : 20px 0px;
+  max-width : 1210px;
+  margin    : auto;
+  //  margin-top : -70px;
+  padding   : 20px 0px;
 
   .header {
     padding-bottom : 10px;
@@ -203,15 +202,15 @@ body {
 }
 
 .error {
-  background: #FF0000;
+  background : #FF0000;
 }
 
-#errors{
+#errors {
   display : none;
 }
 
 .mono-space {
-  font-family: "ubuntu mono"
+  font-family : "ubuntu mono"
 }
 
 .file-path {
@@ -221,26 +220,26 @@ body {
 .control-panel {
   .card(#333333);
 
-  color: #f5f5f5;
-  background-color: #333333;
-  margin: 0px;
-  margin-bottom: -100px;
-  position: fixed;
-  left: 0;
-  bottom: 0;
-  width: 100%;
-  height: 80px;
-  opacity: 0;
+  color            : #f5f5f5;
+  background-color : #333333;
+  margin           : 0px;
+  margin-bottom    : -100px;
+  position         : fixed;
+  left             : 0;
+  bottom           : 0;
+  width            : 100%;
+  height           : 80px;
+  opacity          : 0;
 
-  @media (max-width: @screen-sm-max) {
-    height: initial;
-    position: static;
+  @media (max-width : @screen-sm-max) {
+    height   : initial;
+    position : static;
   }
 }
 
 .tab-content {
   .tab-pane {
-    padding-top: 10px;
+    padding-top : 10px;
   }
 }
 
@@ -251,6 +250,17 @@ body {
   }
 }
 
+.modal-body {
+  table {
+    font-size   : 12px;
+    font-weight : bold;
+
+    i {
+      font-size : 14px;
+    }
+  }
+}
+
 .tooltip {
   .tooltip-inner {
     max-width : 250px;
@@ -259,6 +269,16 @@ body {
 
 dl.info {
   dt, dd {
-    padding-bottom: 5px;
+    padding-bottom : 5px;
   }
 }
+
+.label {
+  &.protocol-torrent {
+    background-color : #00853D;
+  }
+
+  &.protocol-usenet {
+    background-color : #17B1D9;
+  }
+}
\ No newline at end of file
diff --git a/src/UI/Episode/Search/ManualLayout.js b/src/UI/Episode/Search/ManualLayout.js
index 81388dc9f..31f8c6766 100644
--- a/src/UI/Episode/Search/ManualLayout.js
+++ b/src/UI/Episode/Search/ManualLayout.js
@@ -8,8 +8,10 @@ define(
         'Cells/QualityCell',
         'Cells/ApprovalStatusCell',
         'Release/DownloadReportCell',
-        'Release/AgeCell'
-    ], function (Marionette, Backgrid, ReleaseTitleCell, FileSizeCell, QualityCell, ApprovalStatusCell, DownloadReportCell, AgeCell) {
+        'Release/AgeCell',
+        'Release/ProtocolCell',
+        'Release/PeersCell'
+    ], function (Marionette, Backgrid, ReleaseTitleCell, FileSizeCell, QualityCell, ApprovalStatusCell, DownloadReportCell, AgeCell, ProtocolCell, PeersCell) {
 
         return Marionette.Layout.extend({
             template: 'Episode/Search/ManualLayoutTemplate',
@@ -21,6 +23,10 @@ define(
             columns:
                 [
                     {
+                        name     : 'protocol',
+                        label    : 'Source',
+                        cell     : ProtocolCell
+                    },  {
                         name     : 'age',
                         label    : 'Age',
                         cell     : AgeCell
@@ -40,6 +46,11 @@ define(
                         label    : 'Size',
                         cell     : FileSizeCell
                     },
+                    {
+                        name     : 'seeders',
+                        label    : 'Peers',
+                        cell     : PeersCell
+                    },
                     {
                         name     : 'quality',
                         label    : 'Quality',
diff --git a/src/UI/Release/PeersCell.js b/src/UI/Release/PeersCell.js
new file mode 100644
index 000000000..1ac08e1f4
--- /dev/null
+++ b/src/UI/Release/PeersCell.js
@@ -0,0 +1,34 @@
+'use strict';
+
+define(['backgrid'], function (Backgrid) {
+    return Backgrid.Cell.extend({
+
+        className : 'peers-cell',
+
+        render : function () {
+
+            if (this.model.get('protocol') === 'torrent') {
+
+                var seeders = this.model.get('seeders') || 0;
+                var leechers = this.model.get('leechers') || 0;
+
+                var level = 'danger';
+
+                if(seeders > 0){
+                    level='warning';
+                }
+                if(seeders > 10){
+                    level = 'info';
+                }
+                if(seeders > 50){
+                    level = 'primary';
+                }
+
+                this.$el.html('<div class="label label-{2}" title="{0} seeders, {1} leechers">{0} / {1}</div>'.format(seeders, leechers, level));
+            }
+
+            this.delegateEvents();
+            return this;
+        }
+    });
+});
\ No newline at end of file
diff --git a/src/UI/Release/ProtocolCell.js b/src/UI/Release/ProtocolCell.js
new file mode 100644
index 000000000..13645a6d8
--- /dev/null
+++ b/src/UI/Release/ProtocolCell.js
@@ -0,0 +1,28 @@
+'use strict';
+
+define(['backgrid'], function (Backgrid) {
+    return Backgrid.Cell.extend({
+
+        className : 'protocol-cell',
+
+        render : function () {
+            var protocol = this.model.get('protocol') || 'Unknown';
+
+            var label = '??';
+
+            if (protocol) {
+                if (protocol === 'torrent') {
+                    label = 'torrent';
+                }
+                else if (protocol === 'usenet') {
+                    label = 'nzb';
+                }
+
+                this.$el.html('<div class="label label-default protocol-{0}" title="{0}">{1}</div>'.format(protocol, label));
+            }
+
+            this.delegateEvents();
+            return this;
+        }
+    });
+});
\ No newline at end of file
diff --git a/src/UI/Release/ReleaseCollection.js b/src/UI/Release/ReleaseCollection.js
index 796144502..0093e0022 100644
--- a/src/UI/Release/ReleaseCollection.js
+++ b/src/UI/Release/ReleaseCollection.js
@@ -1,4 +1,4 @@
-'use strict';
+'use strict';
 define(
     [
         'backbone.pageable',
@@ -19,7 +19,7 @@ define(
             
             sortMappings: {
                 'quality'       : { sortKey: 'qualityWeight' },
-                'rejections'    : { sortValue: function (model, attr) {
+                'rejections'    : { sortValue: function (model) {
                                         var rejections = model.get('rejections');
                                         var releaseWeight = model.get('releaseWeight');