Repository: pymedusa/Medusa

Issue:
[APP SUBMITTED]: AttributeError: 'dict' object has no attribute 'replace'
so my Auto Post Processing is not working and when I try to do Manual PP, this happens:

### INFO
**Python Version**: `2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:53:40) [MSC v.1500 64 bit (AMD64)]`
**Operating System**: `Windows-7-6.1.7601-SP1`
**Locale**: `cp1252`
**Branch**: [master](../tree/master)
**Database**: `44.12`
**Commit**: pymedusa/Medusa@db3d9d767bbf3fe7c365df20d92099b0cbbd91b4
**Link to Log**: https://gist.github.com/9676ce5b08d10ad0969db89b2764bc06
### ERROR
<pre>
2018-09-08 01:07:49 ERROR    Thread_7 :: [db3d9d7] Exception generated: 'dict' object has no attribute 'replace'
Traceback (most recent call last):
  File "C:\Medusa\Medusa\<a href="../blob/db3d9d767bbf3fe7c365df20d92099b0cbbd91b4/medusa/server/web/core/base.py#L259">medusa\server\web\core\base.py</a>", line 259, in async_call
    result = function(**kwargs)
  File "C:\Medusa\Medusa\<a href="../blob/db3d9d767bbf3fe7c365df20d92099b0cbbd91b4/medusa/server/web/home/post_process.py#L49">medusa\server\web\home\post_process.py</a>", line 49, in processEpisode
    ignore_subs=argToBool(ignore_subs)
  File "C:\Medusa\Medusa\<a href="../blob/db3d9d767bbf3fe7c365df20d92099b0cbbd91b4/medusa/process_tv.py#L138">medusa\process_tv.py</a>", line 138, in process
    postpone = app.POSTPONE_IF_SYNC_FILES and any(sync_files)
  File "C:\Medusa\Medusa\<a href="../blob/db3d9d767bbf3fe7c365df20d92099b0cbbd91b4/medusa/process_tv.py#L135">medusa\process_tv.py</a>", line 135, in <genexpr>
    if is_sync_file(filename))
  File "C:\Medusa\Medusa\<a href="../blob/db3d9d767bbf3fe7c365df20d92099b0cbbd91b4/medusa/helper/common.py#L162">medusa\helper\common.py</a>", line 162, in is_sync_file
    any(fnmatch(filename, match) for match in app.SYNC_FILES))
  File "C:\Medusa\Medusa\<a href="../blob/db3d9d767bbf3fe7c365df20d92099b0cbbd91b4/medusa/helper/common.py#L162">medusa\helper\common.py</a>", line 162, in <genexpr>
    any(fnmatch(filename, match) for match in app.SYNC_FILES))
  File "C:\Medusa\Python\lib\fnmatch.py", line 42, in fnmatch
    pat = os.path.normcase(pat)
  File "C:\Medusa\Medusa\<a href="../blob/db3d9d767bbf3fe7c365df20d92099b0cbbd91b4/medusa/init/filesystem.py#L113">medusa\init\filesystem.py</a>", line 113, in patched_output
    return handle_output(f(*args, **kwargs))
  File "C:\Medusa\Python\lib\ntpath.py", line 47, in normcase
    return s.replace("/", "\\").lower()
AttributeError: 'dict' object has no attribute 'replace'
</pre>
---
_STAFF NOTIFIED_: @pymedusa/support @pymedusa/moderators



Discussion:
@cairobraga 
What is your `Sync File Extensions` configuration under Post Processing config?

![image](https://user-images.githubusercontent.com/10238474/45255997-b9e51880-b398-11e8-995e-854bb7ce1b0c.png)

**Confirmed:**

![image](https://user-images.githubusercontent.com/10238474/45256181-8b1c7180-b39b-11e8-87ec-e39c356dd51f.png)

@cairobraga 
If you can post the following values from your `config.ini`, I'll give you a correct ones to fix the issue for now.

```
sync_files
extra_scripts
allowed_extensions
```

Resolving patch:
diff --git a/medusa/__main__.py b/medusa/__main__.py
--- a/medusa/__main__.py
+++ b/medusa/__main__.py
@@ -95,6 +95,34 @@
 logger = logging.getLogger(__name__)
 
 
+def fix_incorrect_list_values(data):
+    """
+    @TODO: Remove this in a future version.
+
+    Due to a bug introduced in v0.2.9, the value might be a string representing a Python dict.
+    See: https://github.com/pymedusa/Medusa/issues/5155
+
+    Example: `"{u'id': 0, u'value': u'!sync'}"` to `"!sync"`
+    """
+    import ast
+
+    result = []
+    for item in data:
+        if not item:
+            continue
+        if not (item.startswith('{') and item.endswith('}')):
+            # Simple value, don't do anything to it
+            result.append(item)
+            continue
+        try:
+            # Get the value: `{u'id': 0, u'value': u'!sync'}` => `!sync`
+            result.append(ast.literal_eval(item)['value'])
+        except (SyntaxError, KeyError):
+            pass
+
+    return result
+
+
 class Application(object):
     """Main application module."""
 
@@ -604,7 +632,11 @@ def initialize(self, console_logging=True):
             app.RANDOMIZE_PROVIDERS = bool(check_setting_int(app.CFG, 'General', 'randomize_providers', 0))
             app.ALLOW_HIGH_PRIORITY = bool(check_setting_int(app.CFG, 'General', 'allow_high_priority', 1))
             app.SKIP_REMOVED_FILES = bool(check_setting_int(app.CFG, 'General', 'skip_removed_files', 0))
+
             app.ALLOWED_EXTENSIONS = check_setting_list(app.CFG, 'General', 'allowed_extensions', app.ALLOWED_EXTENSIONS)
+            # @TODO: Remove this in a future version.
+            app.ALLOWED_EXTENSIONS = fix_incorrect_list_values(app.ALLOWED_EXTENSIONS)
+
             app.USENET_RETENTION = check_setting_int(app.CFG, 'General', 'usenet_retention', 500)
             app.CACHE_TRIMMING = bool(check_setting_int(app.CFG, 'General', 'cache_trimming', 0))
             app.MAX_CACHE_AGE = check_setting_int(app.CFG, 'General', 'max_cache_age', 30)
@@ -646,7 +678,11 @@ def initialize(self, console_logging=True):
             app.MOVE_ASSOCIATED_FILES = bool(check_setting_int(app.CFG, 'General', 'move_associated_files', 0))
             app.POSTPONE_IF_SYNC_FILES = bool(check_setting_int(app.CFG, 'General', 'postpone_if_sync_files', 1))
             app.POSTPONE_IF_NO_SUBS = bool(check_setting_int(app.CFG, 'General', 'postpone_if_no_subs', 0))
+
             app.SYNC_FILES = check_setting_list(app.CFG, 'General', 'sync_files', app.SYNC_FILES)
+            # @TODO: Remove this in a future version.
+            app.SYNC_FILES = fix_incorrect_list_values(app.SYNC_FILES)
+
             app.NFO_RENAME = bool(check_setting_int(app.CFG, 'General', 'nfo_rename', 1))
             app.CREATE_MISSING_SHOW_DIRS = bool(check_setting_int(app.CFG, 'General', 'create_missing_show_dirs', 0))
             app.ADD_SHOWS_WO_DIR = bool(check_setting_int(app.CFG, 'General', 'add_shows_wo_dir', 0))
@@ -919,6 +955,8 @@ def initialize(self, console_logging=True):
             app.NO_RESTART = bool(check_setting_int(app.CFG, 'General', 'no_restart', 0))
 
             app.EXTRA_SCRIPTS = [x.strip() for x in check_setting_list(app.CFG, 'General', 'extra_scripts')]
+            # @TODO: Remove this in a future version.
+            app.EXTRA_SCRIPTS = fix_incorrect_list_values(app.EXTRA_SCRIPTS)
 
             app.USE_LISTVIEW = bool(check_setting_int(app.CFG, 'General', 'use_listview', 0))
 


Test patch:
diff --git a/themes-default/slim/test/specs/select-list.spec.js b/themes-default/slim/test/specs/select-list.spec.js
--- a/themes-default/slim/test/specs/select-list.spec.js
+++ b/themes-default/slim/test/specs/select-list.spec.js
@@ -20,10 +20,40 @@ test('renders', t => {
     const { localVue, store } = t.context;
     const wrapper = mount(SelectList, {
         localVue,
+        store,
         propsData: {
             listItems: []
-        },
-        store
+        }
+    });
+
+    t.snapshot(wrapper.html());
+});
+
+test.failing('renders with values', t => {
+    const { localVue, store } = t.context;
+
+    const listItems = [
+        'abc',
+        'bcd',
+        'test'
+    ];
+
+    const wrapper = mount(SelectList, {
+        localVue,
+        store,
+        propsData: {
+            listItems
+        }
+    });
+
+    const expectedItems = listItems;
+    const inputWrapperArray = wrapper.findAll('li input[type="text"]');
+
+    t.is(inputWrapperArray.length, expectedItems.length);
+
+    inputWrapperArray.wrappers.forEach((inputWrapper, index) => {
+        const { element } = inputWrapper;
+        t.is(element.value, expectedItems[index]);
     });
 
     t.snapshot(wrapper.html());
diff --git a/themes-default/slim/test/specs/snapshots/select-list.spec.js.md b/themes-default/slim/test/specs/snapshots/select-list.spec.js.md
--- a/themes-default/slim/test/specs/snapshots/select-list.spec.js.md
+++ b/themes-default/slim/test/specs/snapshots/select-list.spec.js.md
@@ -9,3 +9,9 @@ Generated by [AVA](https://ava.li).
 > Snapshot 1
 
     '<div class="select-list max-width"><i title="Switch between a list and comma separated values" class="switch-input glyphicon glyphicon-refresh"></i><ul><div class="new-item"><div class="input-group"><input type="text" placeholder="add new values per line" class="form-control input-sm"><div class="input-group-btn"><div class="btn btn-default input-sm" style="font-size: 14px;"><i title="Add" class="glyphicon glyphicon-plus"></i></div></div></div></div><!----></ul></div>'
+
+## renders with values
+
+> Snapshot 1
+
+    '<div class="select-list max-width"><i title="Switch between a list and comma separated values" class="switch-input glyphicon glyphicon-refresh"></i><ul><li><div class="input-group"><input type="text" class="form-control input-sm"><div class="input-group-btn"><div class="btn btn-default input-sm" style="font-size: 14px;"><i title="Remove" class="glyphicon glyphicon-remove"></i></div></div></div></li><li><div class="input-group"><input type="text" class="form-control input-sm"><div class="input-group-btn"><div class="btn btn-default input-sm" style="font-size: 14px;"><i title="Remove" class="glyphicon glyphicon-remove"></i></div></div></div></li><li><div class="input-group"><input type="text" class="form-control input-sm"><div class="input-group-btn"><div class="btn btn-default input-sm" style="font-size: 14px;"><i title="Remove" class="glyphicon glyphicon-remove"></i></div></div></div></li><div class="new-item"><div class="input-group"><input type="text" placeholder="add new values per line" class="form-control input-sm"><div class="input-group-btn"><div class="btn btn-default input-sm" style="font-size: 14px;"><i title="Add" class="glyphicon glyphicon-plus"></i></div></div></div></div><!----></ul></div>'
diff --git a/themes-default/slim/test/specs/snapshots/select-list.spec.js.snap b/themes-default/slim/test/specs/snapshots/select-list.spec.js.snap
Binary files a/themes-default/slim/test/specs/snapshots/select-list.spec.js.snap and b/themes-default/slim/test/specs/snapshots/select-list.spec.js.snap differ