Repository: Flexget/Flexget

Issue:
Incorrect timeframe plugin behaviour when loading items from an entry_list
### Expected behaviour:
The 1080p is downloaded

### Actual behaviour:
The plugin ignores 1080p and sees only 720p

### Steps to reproduce:
- Step 1: Add movies from rss http://rutor.info/rss.php?cat=7 to list movies_list

#### Config:
```
schedules:
  - tasks: ["download *"]
    interval:
      minutes: 15

web_server: yes

tasks:
  download good movies:
    entry_list: movies_list
    seen_movies:
      scope: local
    seen: local
    seen_info_hash: local
    timeframe:
      wait: 14 day
      on_reached: do_nothing
      target: 1080p
    priority: 5
    imdb:
      min_score: 6.3
      min_votes: 1000
      reject_genres:
        - documentary
        - musical
        - music
        - history
        - biography
        - war
    list_remove:
      - entry_list: movies_list
    download: /downloads
```
  
#### Log:
<details>
 <summary>(click to expand)</summary>

```
2018-10-26 09:16 VERBOSE  task          download good movies REJECTED: `Incredibles 2 (2018) WEB-DL 720p от селезень | iTunes` by timeframe plugin because timeframe waiting
2018-10-26 09:16 VERBOSE  task          download good movies REJECTED: `Incredibles 2 (2018) WEB-DL 1080p от TeamHD | iTunes` by timeframe plugin because timeframe waiting
2018-10-26 09:16 INFO     timeframe     download good movies `incredibles 2 2018`: timeframe waiting for 321h:59min. Currently best is `Incredibles 2 (2018) WEB-DL 720p от селезень | iTunes`.
```

<details><summary>--dump output excerpt</summary>
720p:

```
title            : Incredibles 2 (2018) WEB-DL 720p от селезень | iTunes
url              : http://d.rutor.is/download.php?rss=661218
original_url     : http://d.rutor.is/download.php?rss=661218
accepted_by      : imdb
content-length   : 82000
content_files    : [Incredibles.2.2018.720p.WEB-DL.selezen.mkv]
content_size     : 4012.526089668274
description      : Мультипликация
filename         : [rutor.is]Incredibles.2.2018.720p.WEB-DL.selezen.mkv.torrent
id               : incredibles 2 2018
imdb_id          : tt3606756
quality          : 720p webdl
reason           : timeframe waiting
rejected_by      : timeframe
```

1080p:
```
title            : Incredibles 2 (2018) WEB-DL 1080p от TeamHD | iTunes
url              : http://d.rutor.is/download.php?rss=661219
original_url     : http://d.rutor.is/download.php?rss=661219
accepted_by      : imdb
content-length   : 90640
content_files    : [Incredibles.2.2018.1080p.WEB-DL.TeamHD.mkv]
content_size     : 4444.096870422363
description      : Мультипликация
filename         : [rutor.is]Incredibles.2.2018.1080p.WEB-DL.TeamHD.mkv.torrent
id               : incredibles 2 2018
imdb_id          : tt3606756
quality          : 1080p webdl
raw_title        : Суперсемейка 2 / Incredibles 2 (2018) WEB-DL 1080p от TeamHD | iTunes
reason           : timeframe waiting
rejected_by      : timeframe
```

</details>
</details>

### Additional information:

- FlexGet version: 2.17.5 (first time noticed on 2.17.0, but probably is here for long time)
- Python version: 3.5.3
- Installation method: pip3 install (+docker)
- Using daemon (yes/no): yes
- OS and version: Debian Stretch
Incorrect timeframe plugin behaviour when loading items from an entry_list
### Expected behaviour:
The 1080p is downloaded

### Actual behaviour:
The plugin ignores 1080p and sees only 720p

### Steps to reproduce:
- Step 1: Add movies from rss http://rutor.info/rss.php?cat=7 to list movies_list

#### Config:
```
schedules:
  - tasks: ["download *"]
    interval:
      minutes: 15

web_server: yes

tasks:
  download good movies:
    entry_list: movies_list
    seen_movies:
      scope: local
    seen: local
    seen_info_hash: local
    timeframe:
      wait: 14 day
      on_reached: do_nothing
      target: 1080p
    priority: 5
    imdb:
      min_score: 6.3
      min_votes: 1000
      reject_genres:
        - documentary
        - musical
        - music
        - history
        - biography
        - war
    list_remove:
      - entry_list: movies_list
    download: /downloads
```
  
#### Log:
<details>
 <summary>(click to expand)</summary>

```
2018-10-26 09:16 VERBOSE  task          download good movies REJECTED: `Incredibles 2 (2018) WEB-DL 720p от селезень | iTunes` by timeframe plugin because timeframe waiting
2018-10-26 09:16 VERBOSE  task          download good movies REJECTED: `Incredibles 2 (2018) WEB-DL 1080p от TeamHD | iTunes` by timeframe plugin because timeframe waiting
2018-10-26 09:16 INFO     timeframe     download good movies `incredibles 2 2018`: timeframe waiting for 321h:59min. Currently best is `Incredibles 2 (2018) WEB-DL 720p от селезень | iTunes`.
```

<details><summary>--dump output excerpt</summary>
720p:

```
title            : Incredibles 2 (2018) WEB-DL 720p от селезень | iTunes
url              : http://d.rutor.is/download.php?rss=661218
original_url     : http://d.rutor.is/download.php?rss=661218
accepted_by      : imdb
content-length   : 82000
content_files    : [Incredibles.2.2018.720p.WEB-DL.selezen.mkv]
content_size     : 4012.526089668274
description      : Мультипликация
filename         : [rutor.is]Incredibles.2.2018.720p.WEB-DL.selezen.mkv.torrent
id               : incredibles 2 2018
imdb_id          : tt3606756
quality          : 720p webdl
reason           : timeframe waiting
rejected_by      : timeframe
```

1080p:
```
title            : Incredibles 2 (2018) WEB-DL 1080p от TeamHD | iTunes
url              : http://d.rutor.is/download.php?rss=661219
original_url     : http://d.rutor.is/download.php?rss=661219
accepted_by      : imdb
content-length   : 90640
content_files    : [Incredibles.2.2018.1080p.WEB-DL.TeamHD.mkv]
content_size     : 4444.096870422363
description      : Мультипликация
filename         : [rutor.is]Incredibles.2.2018.1080p.WEB-DL.TeamHD.mkv.torrent
id               : incredibles 2 2018
imdb_id          : tt3606756
quality          : 1080p webdl
raw_title        : Суперсемейка 2 / Incredibles 2 (2018) WEB-DL 1080p от TeamHD | iTunes
reason           : timeframe waiting
rejected_by      : timeframe
```

</details>
</details>

### Additional information:

- FlexGet version: 2.17.5 (first time noticed on 2.17.0, but probably is here for long time)
- Python version: 3.5.3
- Installation method: pip3 install (+docker)
- Using daemon (yes/no): yes
- OS and version: Debian Stretch


Discussion:
Read the documentation: https://flexget.com/Plugins/timeframe

`on_reached: The action to perform on entries which reach the target quality or timeframe has expired. do_nothing won’t act on the entry (undecided). Default is accept`
@cvium hm, as far as I can see — the 1080p was accepted by `imdb`, and then rejected by `timeframe`. What am I missing?..
For now (at least from my point of view) it seems like there is some problem in detecting the best quality, or just something goes wrong with `webdl` quality — I had a movie downloaded yesterday from the very first try:
```
2018-10-25 06:31 INFO     download      download good movies Downloading: Road Trip (2000) BDRip 1080p | D, P, P2, A
```
Unfortunately I have no `--dump` details for this one...
Something does look wrong here, the timeframe plugin shouldn't be rejecting an entry that meets the target. As for the 'on_reached: do_nothing' bit, that also seems fine to me, the imdb plugin should do the accepting.

Are you sure the config was like this when that entry got rejected? Maybe you updated the target and didn't restart the daemon?
`on_waiting: The action to preform on entries which are pending for the target quality. do_nothing won’t act on the entry (undecided). Default is reject`

I'm still not sure I see any problems here... The plugin is configured to reject while waiting for the timeframe to expire and it is configured to _not do anything_ when an entry meets the target quality. Hence, nothing happens and entries are rejected.
> Are you sure the config was like this when that entry got rejected? Maybe you updated the target and didn't restart the daemon?

I'm sure that the config was the same, and the daemon have read it... I haven't changed the config for about a week and I always manually run reload-config after modifications. And actually the daemon is working with `--autoreload-config` option enabled.

> it is configured to not do anything when an entry meets the target quality. Hence, nothing happens and entries are rejected.

But IMDB should've accepted the movie anyway, isn't it? Or when an entry was read from the backlog it arrives already rejected, hence the movies can't be accepted by imdb?

Anyway, one of my questions is "why the timeframe thinks that 720p is the best available option"?..
Hi there! :) I was able to reproduce the problem within test_timeframe, please take a look below. Basically, my problem appears when an entry already has some parsed data `quality` field, but without pre-parsed `quality` the plugin works wrong in another way: it accepts all entries (worse entry isn't rejected).

<details><summary>diff</summary>

```
diff --git a/flexget/tests/test_timeframe.py b/flexget/tests/test_timeframe.py
index 6f36d4078..635c967a1 100644
--- a/flexget/tests/test_timeframe.py
+++ b/flexget/tests/test_timeframe.py
@@ -47,6 +47,22 @@ class TestTimeFrame(object):
               on_reached: accept
             mock:
               - {title: 'Movie.1080p WEB-DL X264 AC3', 'id': 'Movie'}
+          target3:
+            timeframe:
+              wait: 1 hour
+              target: 1080p
+              on_reached: do_nothing
+            mock:
+              - {title: 'Movie (2018) WEB-DL 720p', 'id': 'Movie', 'quality': '720p webdl'}
+              - {title: 'Movie (2018) WEB-DL 1080p', 'id': 'Movie', 'quality': '1080p webdl'}
+          target4:
+            timeframe:
+              wait: 1 hour
+              target: 1080p
+              on_reached: do_nothing
+            mock:
+              - {title: 'Movie (2018) WEB-DL 720p', 'id': 'Movie'}
+              - {title: 'Movie (2018) WEB-DL 1080p', 'id': 'Movie'}
     """

     def test_wait(self, execute_task):
@@ -85,6 +101,19 @@ class TestTimeFrame(object):
             assert len(query) == 1, 'There should be one tracked entity present.'
             assert query[0].status == 'accepted', 'Should be accepted.'

+    def test_accept_only_best_movie(self, manager, execute_task):
+        task = execute_task('target3')
+        entry = task.find_entry('rejected', title='Movie (2018) WEB-DL 720p')
+        assert entry, 'Movie (2018) WEB-DL 720p should be rejected'
+        entry = task.find_entry('accepted', title='Movie (2018) WEB-DL 1080p')
+        assert entry, 'Movie (2018) WEB-DL 1080p should be accepted'
+
+    def test_accept_only_best_movie2(self, manager, execute_task):
+        task = execute_task('target4')
+        entry = task.find_entry('rejected', title='Movie (2018) WEB-DL 720p')
+        assert entry, 'Movie (2018) WEB-DL 720p should be rejected'
+        entry = task.find_entry('accepted', title='Movie (2018) WEB-DL 1080p')
+        assert entry, 'Movie (2018) WEB-DL 1080p should be accepted'

 class TestTimeFrameActions(object):
     config = """
```

</details>
<details><summary>log</summary>

```
conftest.py                 89 INFO     ********** Running task: target3 **********
task.py                    579 DEBUG    executing target3
template.py                 92 DEBUG    Merging template global into task target3
status.py                  110 DEBUG    Adding new task target3
backlog.py                 195 DEBUG    0 entries purged from backlog
logger.py                  133 VERBOSE  Produced 2 entries.
logger.py                  133 VERBOSE  ACCEPTED: `Movie (2018) WEB-DL 720p` by accept_all plugin
logger.py                  133 VERBOSE  ACCEPTED: `Movie (2018) WEB-DL 1080p` by accept_all plugin
timeframe.py               102 DEBUG    Current best for identifier movie is Movie (2018) WEB-DL 720p
logger.py                  133 VERBOSE  REJECTED: `Movie (2018) WEB-DL 720p` by timeframe plugin because timeframe waiting
logger.py                  133 VERBOSE  REJECTED: `Movie (2018) WEB-DL 1080p` by timeframe plugin because timeframe waiting
timeframe.py               135 INFO     `movie`: timeframe waiting for 00h:59min. Currently best is `Movie (2018) WEB-DL 720p`.
backlog.py                 164 DEBUG    Saving Movie (2018) WEB-DL 720p
urlrewriting.py             29 DEBUG    Checking 0 entries
logger.py                  133 VERBOSE  Summary - Accepted: 0 (Rejected: 2 Undecided: 0 Failed: 0)
simple_persistence.py      159 DEBUG    Flushing simple persistence for task target3 to db.
```
```
conftest.py                 89 INFO     ********** Running task: target4 **********
task.py                    579 DEBUG    executing target4
template.py                 92 DEBUG    Merging template global into task target4
status.py                  110 DEBUG    Adding new task target4
backlog.py                 195 DEBUG    0 entries purged from backlog
logger.py                  133 VERBOSE  Produced 2 entries.
logger.py                  133 VERBOSE  ACCEPTED: `Movie (2018) WEB-DL 720p` by accept_all plugin
logger.py                  133 VERBOSE  ACCEPTED: `Movie (2018) WEB-DL 1080p` by accept_all plugin
timeframe.py               102 DEBUG    Current best for identifier movie is Movie (2018) WEB-DL 1080p
timeframe.py               112 DEBUG    timeframe reach target quality 1080p or higher for movie
urlrewriting.py             29 DEBUG    Checking 2 entries
logger.py                  133 VERBOSE  Summary - Accepted: 2 (Rejected: 0 Undecided: 0 Failed: 0)
seen.py                    302 DEBUG    Learned 'Movie (2018) WEB-DL 720p' (field: title, local: 0)
seen.py                    302 DEBUG    Learned 'mock://localhost/mock/-2893712694707200956' (field: url, local: 0)
seen.py                    302 DEBUG    Learned 'Movie (2018) WEB-DL 1080p' (field: title, local: 0)
seen.py                    302 DEBUG    Learned 'mock://localhost/mock/-1072356131469359635' (field: url, local: 0)
simple_persistence.py      159 DEBUG    Flushing simple persistence for task target4 to db.
```

</details>
I've done some investigation: timeframe plugin (and probably any other plugin) is expecting the `quality` field to be type of `Quality`, but when you're loading data from an entry_list — it's stored as `str`. As temporary workaround I'm removing `quality` field using `manipulate` plugin, and seems like it's working. 
Thanks for this investigation! Not sure the best way yet, but we should definitely fix entry_list turning the quality field into a string.
Read the documentation: https://flexget.com/Plugins/timeframe

`on_reached: The action to perform on entries which reach the target quality or timeframe has expired. do_nothing won’t act on the entry (undecided). Default is accept`
@cvium hm, as far as I can see — the 1080p was accepted by `imdb`, and then rejected by `timeframe`. What am I missing?..
For now (at least from my point of view) it seems like there is some problem in detecting the best quality, or just something goes wrong with `webdl` quality — I had a movie downloaded yesterday from the very first try:
```
2018-10-25 06:31 INFO     download      download good movies Downloading: Road Trip (2000) BDRip 1080p | D, P, P2, A
```
Unfortunately I have no `--dump` details for this one...
Something does look wrong here, the timeframe plugin shouldn't be rejecting an entry that meets the target. As for the 'on_reached: do_nothing' bit, that also seems fine to me, the imdb plugin should do the accepting.

Are you sure the config was like this when that entry got rejected? Maybe you updated the target and didn't restart the daemon?
`on_waiting: The action to preform on entries which are pending for the target quality. do_nothing won’t act on the entry (undecided). Default is reject`

I'm still not sure I see any problems here... The plugin is configured to reject while waiting for the timeframe to expire and it is configured to _not do anything_ when an entry meets the target quality. Hence, nothing happens and entries are rejected.
> Are you sure the config was like this when that entry got rejected? Maybe you updated the target and didn't restart the daemon?

I'm sure that the config was the same, and the daemon have read it... I haven't changed the config for about a week and I always manually run reload-config after modifications. And actually the daemon is working with `--autoreload-config` option enabled.

> it is configured to not do anything when an entry meets the target quality. Hence, nothing happens and entries are rejected.

But IMDB should've accepted the movie anyway, isn't it? Or when an entry was read from the backlog it arrives already rejected, hence the movies can't be accepted by imdb?

Anyway, one of my questions is "why the timeframe thinks that 720p is the best available option"?..
Hi there! :) I was able to reproduce the problem within test_timeframe, please take a look below. Basically, my problem appears when an entry already has some parsed data `quality` field, but without pre-parsed `quality` the plugin works wrong in another way: it accepts all entries (worse entry isn't rejected).

<details><summary>diff</summary>

```
diff --git a/flexget/tests/test_timeframe.py b/flexget/tests/test_timeframe.py
index 6f36d4078..635c967a1 100644
--- a/flexget/tests/test_timeframe.py
+++ b/flexget/tests/test_timeframe.py
@@ -47,6 +47,22 @@ class TestTimeFrame(object):
               on_reached: accept
             mock:
               - {title: 'Movie.1080p WEB-DL X264 AC3', 'id': 'Movie'}
+          target3:
+            timeframe:
+              wait: 1 hour
+              target: 1080p
+              on_reached: do_nothing
+            mock:
+              - {title: 'Movie (2018) WEB-DL 720p', 'id': 'Movie', 'quality': '720p webdl'}
+              - {title: 'Movie (2018) WEB-DL 1080p', 'id': 'Movie', 'quality': '1080p webdl'}
+          target4:
+            timeframe:
+              wait: 1 hour
+              target: 1080p
+              on_reached: do_nothing
+            mock:
+              - {title: 'Movie (2018) WEB-DL 720p', 'id': 'Movie'}
+              - {title: 'Movie (2018) WEB-DL 1080p', 'id': 'Movie'}
     """

     def test_wait(self, execute_task):
@@ -85,6 +101,19 @@ class TestTimeFrame(object):
             assert len(query) == 1, 'There should be one tracked entity present.'
             assert query[0].status == 'accepted', 'Should be accepted.'

+    def test_accept_only_best_movie(self, manager, execute_task):
+        task = execute_task('target3')
+        entry = task.find_entry('rejected', title='Movie (2018) WEB-DL 720p')
+        assert entry, 'Movie (2018) WEB-DL 720p should be rejected'
+        entry = task.find_entry('accepted', title='Movie (2018) WEB-DL 1080p')
+        assert entry, 'Movie (2018) WEB-DL 1080p should be accepted'
+
+    def test_accept_only_best_movie2(self, manager, execute_task):
+        task = execute_task('target4')
+        entry = task.find_entry('rejected', title='Movie (2018) WEB-DL 720p')
+        assert entry, 'Movie (2018) WEB-DL 720p should be rejected'
+        entry = task.find_entry('accepted', title='Movie (2018) WEB-DL 1080p')
+        assert entry, 'Movie (2018) WEB-DL 1080p should be accepted'

 class TestTimeFrameActions(object):
     config = """
```

</details>
<details><summary>log</summary>

```
conftest.py                 89 INFO     ********** Running task: target3 **********
task.py                    579 DEBUG    executing target3
template.py                 92 DEBUG    Merging template global into task target3
status.py                  110 DEBUG    Adding new task target3
backlog.py                 195 DEBUG    0 entries purged from backlog
logger.py                  133 VERBOSE  Produced 2 entries.
logger.py                  133 VERBOSE  ACCEPTED: `Movie (2018) WEB-DL 720p` by accept_all plugin
logger.py                  133 VERBOSE  ACCEPTED: `Movie (2018) WEB-DL 1080p` by accept_all plugin
timeframe.py               102 DEBUG    Current best for identifier movie is Movie (2018) WEB-DL 720p
logger.py                  133 VERBOSE  REJECTED: `Movie (2018) WEB-DL 720p` by timeframe plugin because timeframe waiting
logger.py                  133 VERBOSE  REJECTED: `Movie (2018) WEB-DL 1080p` by timeframe plugin because timeframe waiting
timeframe.py               135 INFO     `movie`: timeframe waiting for 00h:59min. Currently best is `Movie (2018) WEB-DL 720p`.
backlog.py                 164 DEBUG    Saving Movie (2018) WEB-DL 720p
urlrewriting.py             29 DEBUG    Checking 0 entries
logger.py                  133 VERBOSE  Summary - Accepted: 0 (Rejected: 2 Undecided: 0 Failed: 0)
simple_persistence.py      159 DEBUG    Flushing simple persistence for task target3 to db.
```
```
conftest.py                 89 INFO     ********** Running task: target4 **********
task.py                    579 DEBUG    executing target4
template.py                 92 DEBUG    Merging template global into task target4
status.py                  110 DEBUG    Adding new task target4
backlog.py                 195 DEBUG    0 entries purged from backlog
logger.py                  133 VERBOSE  Produced 2 entries.
logger.py                  133 VERBOSE  ACCEPTED: `Movie (2018) WEB-DL 720p` by accept_all plugin
logger.py                  133 VERBOSE  ACCEPTED: `Movie (2018) WEB-DL 1080p` by accept_all plugin
timeframe.py               102 DEBUG    Current best for identifier movie is Movie (2018) WEB-DL 1080p
timeframe.py               112 DEBUG    timeframe reach target quality 1080p or higher for movie
urlrewriting.py             29 DEBUG    Checking 2 entries
logger.py                  133 VERBOSE  Summary - Accepted: 2 (Rejected: 0 Undecided: 0 Failed: 0)
seen.py                    302 DEBUG    Learned 'Movie (2018) WEB-DL 720p' (field: title, local: 0)
seen.py                    302 DEBUG    Learned 'mock://localhost/mock/-2893712694707200956' (field: url, local: 0)
seen.py                    302 DEBUG    Learned 'Movie (2018) WEB-DL 1080p' (field: title, local: 0)
seen.py                    302 DEBUG    Learned 'mock://localhost/mock/-1072356131469359635' (field: url, local: 0)
simple_persistence.py      159 DEBUG    Flushing simple persistence for task target4 to db.
```

</details>
I've done some investigation: timeframe plugin (and probably any other plugin) is expecting the `quality` field to be type of `Quality`, but when you're loading data from an entry_list — it's stored as `str`. As temporary workaround I'm removing `quality` field using `manipulate` plugin, and seems like it's working. 
Thanks for this investigation! Not sure the best way yet, but we should definitely fix entry_list turning the quality field into a string.

Resolving patch:
diff --git a/flexget/plugins/metainfo/quality.py b/flexget/plugins/metainfo/quality.py
--- a/flexget/plugins/metainfo/quality.py
+++ b/flexget/plugins/metainfo/quality.py
@@ -25,7 +25,12 @@ def on_task_metainfo(self, task, config):
         if config is False:
             return
         for entry in task.entries:
-            entry.register_lazy_func(self.get_quality, ['quality'])
+            if isinstance(entry.get('quality', eval_lazy=False), str):
+                log.debug('Quality is already set to %s for %s, but has not been instantiated properly.' %
+                          (entry['quality'], entry['title']))
+                entry['quality'] = qualities.Quality(entry.get('quality', eval_lazy=False))
+            else:
+                entry.register_lazy_func(self.get_quality, ['quality'])
 
     def get_quality(self, entry):
         if entry.get('quality', eval_lazy=False):


Test patch:
diff --git a/flexget/tests/test_qualities.py b/flexget/tests/test_qualities.py
--- a/flexget/tests/test_qualities.py
+++ b/flexget/tests/test_qualities.py
@@ -161,6 +161,11 @@ class TestFilterQuality(object):
             quality: "<=cam <HR"
           min_max:
             quality: HR-720i
+          quality_str:
+            template: no_global
+            mock:
+              - {title: 'Test S01E01 HDTV 1080p', quality: 'hdtv 1080p dd+5.1'}
+            accept_all: yes
     """
 
     @pytest.fixture(scope='class', params=['internal', 'guessit'], ids=['internal', 'guessit'])
@@ -212,6 +217,12 @@ def test_min_max(self, execute_task):
         assert len(task.rejected) == 3, 'wrong number of entries rejected'
         assert len(task.accepted) == 1, 'wrong number of entries accepted'
 
+    def test_quality_string(self, execute_task):
+        task = execute_task('quality_str')
+        entry = task.find_entry('accepted', title='Test S01E01 HDTV 1080p')
+        assert isinstance(entry['quality'], Quality), 'Wrong quality type, should be Quality not str'
+        assert str(entry['quality']) == '1080p hdtv dd+5.1'
+
 
 class TestQualityAudio(object):
     config = """