Repository: ipython/ipython

Issue:
Notebook saving fails on smb share
I'm trying to use the ipython notebook on a mounted smb share, but I can’t use it since ipython uses `shutil.copy2` which fails (due to the use of  `os.chmod`). A solution would probably be to use `shutil.copyfile` as a fallback, but I haven't tested it.
## Traceback

`Traceback (most recent call last):
  File "/home/me/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/tornado/web.py", line 1141, in _when_complete
    callback()
  File "/home/me/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/tornado/web.py", line 1162, in _execute_method
    self._when_complete(method(*self.path_args, **self.path_kwargs),
  File "/home/me/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/tornado/web.py", line 2297, in wrapper
    return method(self, *args, **kwargs)
  File "/home/me/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/IPython/html/services/notebooks/handlers.py", line 107, in post
    checkpoint = nbm.create_checkpoint(notebook_id)
  File "/home/me/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/IPython/html/services/notebooks/filenbmanager.py", line 315, in create_checkpoint
    shutil.copy2(nb_path, cp_path)
  File "/home/me/Canopy/appdata/canopy-1.3.0.1715.rh5-x86_64/lib/python2.7/shutil.py", line 131, in copy2
    copystat(src, dst)
  File "/home/me/Canopy/appdata/canopy-1.3.0.1715.rh5-x86_64/lib/python2.7/shutil.py", line 100, in copystat
    os.chmod(dst, mode)
OSError: [Errno 95] Operation not supported: '/run/user/1000/gvfs/smb-share/calculations/dipoleTrap/.ipynb_checkpoints/dipole-checkpoint.ipynb'`



Discussion:
Arg. copy2 is definitely what we want when available, so we should use the fallback route.


Resolving patch:
diff --git a/IPython/html/services/notebooks/filenbmanager.py b/IPython/html/services/notebooks/filenbmanager.py
--- a/IPython/html/services/notebooks/filenbmanager.py
+++ b/IPython/html/services/notebooks/filenbmanager.py
@@ -85,7 +85,18 @@ def _checkpoint_dir_changed(self, name, old, new):
                 os.mkdir(new)
             except:
                 raise TraitError("Couldn't create checkpoint dir %r" % new)
-
+    
+    def _copy(self, src, dest):
+        """copy src to dest
+        
+        like shutil.copy2, but log errors in copystat
+        """
+        shutil.copyfile(src, dest)
+        try:
+            shutil.copystat(src, dest)
+        except OSError as e:
+            self.log.debug("copystat on %s failed", dest, exc_info=True)
+    
     def get_notebook_names(self, path=''):
         """List all notebook names in the notebook dir and path."""
         path = path.strip('/')
@@ -432,7 +443,7 @@ def create_checkpoint(self, name, path=''):
         self.log.debug("creating checkpoint for notebook %s", name)
         if not os.path.exists(self.checkpoint_dir):
             os.mkdir(self.checkpoint_dir)
-        shutil.copy2(nb_path, cp_path)
+        self._copy(nb_path, cp_path)
         
         # return the checkpoint info
         return self.get_checkpoint_model(checkpoint_id, name, path)
@@ -465,7 +476,7 @@ def restore_checkpoint(self, checkpoint_id, name, path=''):
         # ensure notebook is readable (never restore from an unreadable notebook)
         with io.open(cp_path, 'r', encoding='utf-8') as f:
             current.read(f, u'json')
-        shutil.copy2(cp_path, nb_path)
+        self._copy(cp_path, nb_path)
         self.log.debug("copying %s -> %s", cp_path, nb_path)
     
     def delete_checkpoint(self, checkpoint_id, name, path=''):


Test patch: