Repository: spyder-ide/spyder
Pull request: PR: Fix OSError when saving files on Linux

<!--- Make sure to read the Contributing Guidelines:                   --->
<!--- https://github.com/spyder-ide/spyder/blob/master/CONTRIBUTING.md --->
<!--- and follow PEP 8, PEP 257 and Spyder's code style:               --->
<!--- https://github.com/spyder-ide/spyder/wiki/Dev:-Coding-Style      --->

## Description of Changes


* [ ] Wrote at least one-line docstrings (for any new functions)
* [ ] Added unit test(s) covering the changes (if testable)
<!--- Remember that an image/animation is worth a thousand words! --->
* [ ] Included a screenshot or animation (if affecting the UI, see [Licecap](https://www.cockos.com/licecap/))


<!--- Explain what you've done and why --->
Catch a system error and write the file normally. This happens because some systems doesn't support the option to sync directories.



### Issue(s) Resolved

<!--- List the issue(s) below, in the form "Fixes #1234"; one per line --->

Fixes #10208 


### Affirmation

By submitting this Pull Request or typing my (user)name below,
I affirm the [Developer Certificate of Origin](https://developercertificate.org)
with respect to all commits and content included in this PR,
and understand I am releasing the same under Spyder's MIT (Expat) license.

<!--- TYPE YOUR USER/NAME AFTER THE FOLLOWING: --->
I certify the above statement is true and correct: Steff456

<!--- Thanks for your help making Spyder better for everyone! --->


Problem statement:
OSError when saving files on Linux
## Problem Description

Attempting to save a .py script in spyder4 beta5 leads to error. 

Save Error
Unable to save file 'test_save.py'
Error message:
[Errno 22] Invalid argument

Apologies if simple issue.

### What steps reproduce the problem?

1.  Open spyder4 in terminal
2.  Create new script
3.  Attempt to save script

### What is the expected output? What do you see instead?
Expect to save script without problem.
Instead, see error box as above, resulting in no save.


### Paste Traceback/Error Below (if applicable)
<!--- Copy from error dialog or View > Panes > Internal Console --->

```python
ERROR:spyder.plugins.editor.widgets.autosaveerror:Error while autosaving /scratch/yms23/netscratch/PhD/Python/Scripts/test_save.py to /home/yms23/.config/spyder-py3-dev/autosave/test_save.py
Traceback (most recent call last):
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/spyder/plugins/editor/widgets/editor.py", line 1818, in save
    self._write_to_file(finfo, finfo.filename)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/spyder/plugins/editor/widgets/editor.py", line 1777, in _write_to_file
    fileinfo.encoding = encoding.write(txt, filename, fileinfo.encoding)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/spyder/utils/encoding.py", line 251, in write
    textfile.write(text)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/contextlib.py", line 119, in __exit__
    next(self.gen)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/atomicwrites/__init__.py", line 159, in _open
    self.commit(f)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/atomicwrites/__init__.py", line 192, in commit
    replace_atomic(f.name, self._path)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/atomicwrites/__init__.py", line 93, in replace_atomic
    return _replace_atomic(src, dst)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/atomicwrites/__init__.py", line 50, in _replace_atomic
    _sync_directory(os.path.normpath(os.path.dirname(dst)))
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/atomicwrites/__init__.py", line 44, in _sync_directory
    _proper_fsync(fd)
OSError: [Errno 22] Invalid argument

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/spyder/plugins/editor/utils/autosave.py", line 362, in autosave
    self.stack._write_to_file(finfo, autosave_filename)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/spyder/plugins/editor/widgets/editor.py", line 1777, in _write_to_file
    fileinfo.encoding = encoding.write(txt, filename, fileinfo.encoding)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/spyder/utils/encoding.py", line 251, in write
    textfile.write(text)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/contextlib.py", line 119, in __exit__
    next(self.gen)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/atomicwrites/__init__.py", line 159, in _open
    self.commit(f)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/atomicwrites/__init__.py", line 192, in commit
    replace_atomic(f.name, self._path)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/atomicwrites/__init__.py", line 93, in replace_atomic
    return _replace_atomic(src, dst)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/atomicwrites/__init__.py", line 50, in _replace_atomic
    _sync_directory(os.path.normpath(os.path.dirname(dst)))
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/atomicwrites/__init__.py", line 44, in _sync_directory
    _proper_fsync(fd)
OSError: [Errno 22] Invalid argument
Traceback (most recent call last):
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/spyder/plugins/editor/utils/autosave.py", line 116, in do_autosave
    stack.autosave.autosave_all()
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/spyder/plugins/editor/utils/autosave.py", line 374, in autosave_all
    self.maybe_autosave(index)
  File "/home/yms23/.conda/envs/spyder4beta/lib/python3.7/site-packages/spyder/plugins/editor/utils/autosave.py", line 338, in maybe_autosave
    autosave_hash = self.file_hashes[autosave_filename]
KeyError: '/home/yms23/.config/spyder-py3-dev/autosave/test_save.py'
```

## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version:  4.0.0b5
* Python version:  3.7.4
* Qt version:  5.9.6
* PyQt version:  5.9.2
* Operating System name/version:  Linux 4.4.0-161-generic 

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

cloudpickle >=0.5.0          :  1.2.1 (OK)
pygments >=2.0               :  2.4.2 (OK)
qtconsole >=4.5.5            :  4.5.5 (OK)
nbconvert >=4.0              :  5.5.0 (OK)
sphinx >=0.6.6               :  2.1.2 (OK)
pylint >=0.25                :  2.3.1 (OK)
psutil >=0.3                 :  5.6.3 (OK)
qtawesome >=0.5.7            :  0.5.7 (OK)
qtpy >=1.5.0                 :  1.9.0 (OK)
pickleshare >=0.4            :  0.7.5 (OK)
zmq >=17                     :  18.1.0 (OK)
chardet >=2.0.0              :  3.0.4 (OK)
numpydoc >=0.6.0             :  0.9.1 (OK)
spyder_kernels >=1.5.0;<2.0.0:  1.5.0 (OK)
qdarkstyle >=2.7             :  2.7 (OK)
atomicwrites >=1.2.0         :  1.3.0 (OK)
diff_match_patch >=20181111  :  20181111 (OK)
watchdog                     :  None (OK)
keyring                      :  None (OK)
pexpect >=4.4.0              :  4.7.0 (OK)
pympler                      :  None (OK)
sympy >=0.7.3                :  None (NOK)
cython >=0.21                :  None (NOK)
IPython >=4.0                :  7.7.0 (OK)
matplotlib >=2.0.0           :  3.1.0 (OK)
pandas >=0.13.1              :  0.25.0 (OK)
numpy >=1.7                  :  1.16.4 (OK)
scipy >=0.17.0               :  1.3.1 (OK)
pyls >=0.28.2;<0.29.0        :  0.28.3 (OK)
xdg >=0.26                   :  0.26 (OK)
rtree >=0.8.3                :  0.8.3 (OK)

Hints from issue discussion:
This happens because some filesystems don't support the option to sync directories (e.g. network file systems), as described in issue https://github.com/untitaker/python-atomicwrites/issues/17

@andfoy, please take care of this one. We basically need to catch the error thrown by `atomicwrites` and perform a regular write instead. Code to catch that error is shown in PR https://github.com/untitaker/python-atomicwrites/pull/28

@matthew-shin, until this problem is fixed I'm afraid you'll have to keep using Spyder 3.3.6.
Unfortunate. Will revert to spyder3; thanks for the response!
Sorry @andfoy, let's assign this one to @steff456 because it should be relatively easy.

Commit message: add support for python 2

Patch to review:
diff --git a/spyder/plugins/editor/widgets/editor.py b/spyder/plugins/editor/widgets/editor.py
index 03641e5499f..1f9c36e4a07 100644
--- a/spyder/plugins/editor/widgets/editor.py
+++ b/spyder/plugins/editor/widgets/editor.py
@@ -18,6 +18,7 @@
 import os.path as osp
 import sys
 import unicodedata
+import codecs
 
 # Third party imports
 import qdarkstyle
@@ -35,7 +36,8 @@
 from spyder.config.gui import config_shortcut, is_dark_interface, get_shortcut
 from spyder.config.utils import (get_edit_filetypes, get_edit_filters,
                                  get_filter, is_kde_desktop, is_anaconda)
-from spyder.py3compat import qbytearray_to_str, to_text_string, MutableSequence
+from spyder.py3compat import (qbytearray_to_str, to_text_string,
+                              MutableSequence, PY2)
 from spyder.utils import icon_manager as ima
 from spyder.utils import encoding, sourcecode, syntaxhighlighters
 from spyder.utils.qthelpers import (add_actions, create_action,
@@ -1774,7 +1776,19 @@ def _write_to_file(self, fileinfo, filename):
         correct encoding without doing any error handling.
         """
         txt = fileinfo.editor.get_text_with_eol()
-        fileinfo.encoding = encoding.write(txt, filename, fileinfo.encoding)
+        try:
+            fileinfo.encoding = encoding.write(txt, filename,
+                                               fileinfo.encoding)
+        except OSError:
+            # Some filesystems don't support the option to sync directories
+            # issue untitaker/python-atomicwrites#17
+            if PY2:
+                with codecs.open(filename, 'w',
+                                 encoding=fileinfo.encoding) as f:
+                    f.write(txt)
+            else:
+                with open(filename, 'w', encoding=fileinfo.encoding) as f:
+                    f.write(txt)
 
     def save(self, index=None, force=False):
         """Write text of editor to a file.


Code review comments:
diff_hunk: @@ -1774,7 +1776,19 @@ def _write_to_file(self, fileinfo, filename):
         correct encoding without doing any error handling.
         """
         txt = fileinfo.editor.get_text_with_eol()
-        fileinfo.encoding = encoding.write(txt, filename, fileinfo.encoding)
+        try:
+            fileinfo.encoding = encoding.write(txt, filename,
path: spyder/plugins/editor/widgets/editor.py
text: @user1:
@author, please move the logic you added here directly to the `encoding.write` function because that function is used to save other files too (besides the ones in the editor).