##// END OF EJS Templates
fixed notebook rename after nbmanager refactor
Zachary Sailer -
Show More
@@ -1,350 +1,350 b''
1 """A notebook manager that uses the local file system for storage.
1 """A notebook manager that uses the local file system for storage.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 * Zach Sailer
6 * Zach Sailer
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import datetime
20 import datetime
21 import io
21 import io
22 import os
22 import os
23 import glob
23 import glob
24 import shutil
24 import shutil
25
25
26 from unicodedata import normalize
26 from unicodedata import normalize
27
27
28 from tornado import web
28 from tornado import web
29
29
30 from .nbmanager import NotebookManager
30 from .nbmanager import NotebookManager
31 from IPython.nbformat import current
31 from IPython.nbformat import current
32 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
32 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
33 from IPython.utils import tz
33 from IPython.utils import tz
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Classes
36 # Classes
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 class FileNotebookManager(NotebookManager):
39 class FileNotebookManager(NotebookManager):
40
40
41 save_script = Bool(False, config=True,
41 save_script = Bool(False, config=True,
42 help="""Automatically create a Python script when saving the notebook.
42 help="""Automatically create a Python script when saving the notebook.
43
43
44 For easier use of import, %run and %load across notebooks, a
44 For easier use of import, %run and %load across notebooks, a
45 <notebook-name>.py script will be created next to any
45 <notebook-name>.py script will be created next to any
46 <notebook-name>.ipynb on each save. This can also be set with the
46 <notebook-name>.ipynb on each save. This can also be set with the
47 short `--script` flag.
47 short `--script` flag.
48 """
48 """
49 )
49 )
50
50
51 checkpoint_dir = Unicode(config=True,
51 checkpoint_dir = Unicode(config=True,
52 help="""The location in which to keep notebook checkpoints
52 help="""The location in which to keep notebook checkpoints
53
53
54 By default, it is notebook-dir/.ipynb_checkpoints
54 By default, it is notebook-dir/.ipynb_checkpoints
55 """
55 """
56 )
56 )
57 def _checkpoint_dir_default(self):
57 def _checkpoint_dir_default(self):
58 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
58 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
59
59
60 def _checkpoint_dir_changed(self, name, old, new):
60 def _checkpoint_dir_changed(self, name, old, new):
61 """do a bit of validation of the checkpoint dir"""
61 """do a bit of validation of the checkpoint dir"""
62 if not os.path.isabs(new):
62 if not os.path.isabs(new):
63 # If we receive a non-absolute path, make it absolute.
63 # If we receive a non-absolute path, make it absolute.
64 abs_new = os.path.abspath(new)
64 abs_new = os.path.abspath(new)
65 self.checkpoint_dir = abs_new
65 self.checkpoint_dir = abs_new
66 return
66 return
67 if os.path.exists(new) and not os.path.isdir(new):
67 if os.path.exists(new) and not os.path.isdir(new):
68 raise TraitError("checkpoint dir %r is not a directory" % new)
68 raise TraitError("checkpoint dir %r is not a directory" % new)
69 if not os.path.exists(new):
69 if not os.path.exists(new):
70 self.log.info("Creating checkpoint dir %s", new)
70 self.log.info("Creating checkpoint dir %s", new)
71 try:
71 try:
72 os.mkdir(new)
72 os.mkdir(new)
73 except:
73 except:
74 raise TraitError("Couldn't create checkpoint dir %r" % new)
74 raise TraitError("Couldn't create checkpoint dir %r" % new)
75
75
76 def get_notebook_names(self, path='/'):
76 def get_notebook_names(self, path='/'):
77 """List all notebook names in the notebook dir and path."""
77 """List all notebook names in the notebook dir and path."""
78 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
78 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
79 names = [os.path.basename(name)
79 names = [os.path.basename(name)
80 for name in names]
80 for name in names]
81 return names
81 return names
82
82
83 def increment_filename(self, basename, path='/'):
83 def increment_filename(self, basename, path='/'):
84 """Return a non-used filename of the form basename<int>."""
84 """Return a non-used filename of the form basename<int>."""
85 i = 0
85 i = 0
86 while True:
86 while True:
87 name = u'%s%i.ipynb' % (basename,i)
87 name = u'%s%i.ipynb' % (basename,i)
88 os_path = self.get_os_path(name, path)
88 os_path = self.get_os_path(name, path)
89 if not os.path.isfile(os_path):
89 if not os.path.isfile(os_path):
90 break
90 break
91 else:
91 else:
92 i = i+1
92 i = i+1
93 return name
93 return name
94
94
95 def notebook_exists(self, name, path='/'):
95 def notebook_exists(self, name, path='/'):
96 """Returns a True if the notebook exists. Else, returns False.
96 """Returns a True if the notebook exists. Else, returns False.
97
97
98 Parameters
98 Parameters
99 ----------
99 ----------
100 name : string
100 name : string
101 The name of the notebook you are checking.
101 The name of the notebook you are checking.
102 path : string
102 path : string
103 The relative path to the notebook (with '/' as separator)
103 The relative path to the notebook (with '/' as separator)
104
104
105 Returns
105 Returns
106 -------
106 -------
107 bool
107 bool
108 """
108 """
109 path = self.get_os_path(name, path='/')
109 path = self.get_os_path(name, path='/')
110 return os.path.isfile(path)
110 return os.path.isfile(path)
111
111
112 def list_notebooks(self, path):
112 def list_notebooks(self, path):
113 """Returns a list of dictionaries that are the standard model
113 """Returns a list of dictionaries that are the standard model
114 for all notebooks in the relative 'path'.
114 for all notebooks in the relative 'path'.
115
115
116 Parameters
116 Parameters
117 ----------
117 ----------
118 path : str
118 path : str
119 the URL path that describes the relative path for the
119 the URL path that describes the relative path for the
120 listed notebooks
120 listed notebooks
121
121
122 Returns
122 Returns
123 -------
123 -------
124 notebooks : list of dicts
124 notebooks : list of dicts
125 a list of the notebook models without 'content'
125 a list of the notebook models without 'content'
126 """
126 """
127 notebook_names = self.get_notebook_names(path)
127 notebook_names = self.get_notebook_names(path)
128 notebooks = []
128 notebooks = []
129 for name in notebook_names:
129 for name in notebook_names:
130 model = self.get_notebook_model(name, path, content=False)
130 model = self.get_notebook_model(name, path, content=False)
131 notebooks.append(model)
131 notebooks.append(model)
132 notebooks = sorted(notebooks, key=lambda item: item['name'])
132 notebooks = sorted(notebooks, key=lambda item: item['name'])
133 return notebooks
133 return notebooks
134
134
135 def get_notebook_model(self, name, path='/', content=True):
135 def get_notebook_model(self, name, path='/', content=True):
136 """ Takes a path and name for a notebook and returns it's model
136 """ Takes a path and name for a notebook and returns it's model
137
137
138 Parameters
138 Parameters
139 ----------
139 ----------
140 name : str
140 name : str
141 the name of the notebook
141 the name of the notebook
142 path : str
142 path : str
143 the URL path that describes the relative path for
143 the URL path that describes the relative path for
144 the notebook
144 the notebook
145
145
146 Returns
146 Returns
147 -------
147 -------
148 model : dict
148 model : dict
149 the notebook model. If contents=True, returns the 'contents'
149 the notebook model. If contents=True, returns the 'contents'
150 dict in the model as well.
150 dict in the model as well.
151 """
151 """
152 os_path = self.get_os_path(name, path)
152 os_path = self.get_os_path(name, path)
153 if not os.path.isfile(os_path):
153 if not os.path.isfile(os_path):
154 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
154 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
155 info = os.stat(os_path)
155 info = os.stat(os_path)
156 last_modified = tz.utcfromtimestamp(info.st_mtime)
156 last_modified = tz.utcfromtimestamp(info.st_mtime)
157 # Create the notebook model.
157 # Create the notebook model.
158 model ={}
158 model ={}
159 model['name'] = name
159 model['name'] = name
160 model['path'] = path
160 model['path'] = path
161 model['last_modified'] = last_modified
161 model['last_modified'] = last_modified
162 if content is True:
162 if content is True:
163 with open(os_path, 'r') as f:
163 with open(os_path, 'r') as f:
164 try:
164 try:
165 nb = current.read(f, u'json')
165 nb = current.read(f, u'json')
166 except Exception as e:
166 except Exception as e:
167 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
167 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
168 model['content'] = nb
168 model['content'] = nb
169 return model
169 return model
170
170
171 def save_notebook_model(self, model, name, path='/'):
171 def save_notebook_model(self, model, name, path='/'):
172 """Save the notebook model and return the model with no content."""
172 """Save the notebook model and return the model with no content."""
173
173
174 if 'content' not in model:
174 if 'content' not in model:
175 raise web.HTTPError(400, u'No notebook JSON data provided')
175 raise web.HTTPError(400, u'No notebook JSON data provided')
176
176
177 new_path = model.get('path', path)
177 new_path = model.get('path', path)
178 new_name = model.get('name', name)
178 new_name = model.get('name', name)
179
179
180 if path != new_path or name != new_name:
180 if path != new_path or name != new_name:
181 self.rename_notebook(name, path, new_name, new_path)
181 self.rename_notebook(name, path, new_name, new_path)
182
182
183 # Save the notebook file
183 # Save the notebook file
184 os_path = self.get_os_path(new_name, new_path)
184 os_path = self.get_os_path(new_name, new_path)
185 nb = current.to_notebook_json(model['content'])
185 nb = current.to_notebook_json(model['content'])
186 if 'name' in nb['metadata']:
186 if 'name' in nb['metadata']:
187 nb['metadata']['name'] = u''
187 nb['metadata']['name'] = u''
188 try:
188 try:
189 self.log.debug("Autosaving notebook %s", os_path)
189 self.log.debug("Autosaving notebook %s", os_path)
190 with open(os_path, 'w') as f:
190 with open(os_path, 'w') as f:
191 current.write(nb, f, u'json')
191 current.write(nb, f, u'json')
192 except Exception as e:
192 except Exception as e:
193 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
193 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
194
194
195 # Save .py script as well
195 # Save .py script as well
196 if self.save_script:
196 if self.save_script:
197 py_path = os.path.splitext(os_path)[0] + '.py'
197 py_path = os.path.splitext(os_path)[0] + '.py'
198 self.log.debug("Writing script %s", py_path)
198 self.log.debug("Writing script %s", py_path)
199 try:
199 try:
200 with io.open(py_path, 'w', encoding='utf-8') as f:
200 with io.open(py_path, 'w', encoding='utf-8') as f:
201 current.write(model, f, u'py')
201 current.write(model, f, u'py')
202 except Exception as e:
202 except Exception as e:
203 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
203 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
204
204
205 model = self.get_notebook_model(name, path, content=False)
205 model = self.get_notebook_model(name, path, content=False)
206 return model
206 return model
207
207
208 def update_notebook_model(self, model, name, path='/'):
208 def update_notebook_model(self, model, name, path='/'):
209 """Update the notebook's path and/or name"""
209 """Update the notebook's path and/or name"""
210 new_name = model.get('name', name)
210 new_name = model.get('name', name)
211 new_path = model.get('path', path)
211 new_path = model.get('path', path)
212 if path != new_path or name != new_name:
212 if path != new_path or name != new_name:
213 self.rename_notebook(name, path, new_name, new_path)
213 self.rename_notebook(name, path, new_name, new_path)
214 model = self.get_notebook_model(new_name, new_path, content=False)
214 model = self.get_notebook_model(new_name, new_path, content=False)
215 return model
215 return model
216
216
217 def delete_notebook_model(self, name, path='/'):
217 def delete_notebook_model(self, name, path='/'):
218 """Delete notebook by name and path."""
218 """Delete notebook by name and path."""
219 os_path = self.get_os_path(name, path)
219 os_path = self.get_os_path(name, path)
220 if not os.path.isfile(os_path):
220 if not os.path.isfile(os_path):
221 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
221 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
222
222
223 # clear checkpoints
223 # clear checkpoints
224 for checkpoint in self.list_checkpoints(name, path):
224 for checkpoint in self.list_checkpoints(name, path):
225 checkpoint_id = checkpoint['checkpoint_id']
225 checkpoint_id = checkpoint['checkpoint_id']
226 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
226 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
227 if os.path.isfile(cp_path):
227 if os.path.isfile(cp_path):
228 self.log.debug("Unlinking checkpoint %s", cp_path)
228 self.log.debug("Unlinking checkpoint %s", cp_path)
229 os.unlink(cp_path)
229 os.unlink(cp_path)
230
230
231 self.log.debug("Unlinking notebook %s", os_path)
231 self.log.debug("Unlinking notebook %s", os_path)
232 os.unlink(os_path)
232 os.unlink(os_path)
233
233
234 def rename_notebook(self, old_name, old_path, new_name, new_path):
234 def rename_notebook(self, old_name, old_path, new_name, new_path):
235 """Rename a notebook."""
235 """Rename a notebook."""
236 if new_name == old_name and new_path == old_path:
236 if new_name == old_name and new_path == old_path:
237 return
237 return
238
238
239 new_os_path = self.get_os_path(new_name, new_path)
239 new_os_path = self.get_os_path(new_name, new_path)
240 old_os_path = self.get_os_path(old_name, old_path)
240 old_os_path = self.get_os_path(old_name, old_path)
241
241
242 # Should we proceed with the move?
242 # Should we proceed with the move?
243 if os.path.isfile(new_os_path):
243 if os.path.isfile(new_os_path):
244 raise web.HTTPError(409, u'Notebook with name already exists: ' % new_os_path)
244 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
245 if self.save_script:
245 if self.save_script:
246 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
246 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
247 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
247 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
248 if os.path.isfile(new_py_path):
248 if os.path.isfile(new_py_path):
249 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
249 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
250
250
251 # Move the notebook file
251 # Move the notebook file
252 try:
252 try:
253 os.rename(old_os_path, new_os_path)
253 os.rename(old_os_path, new_os_path)
254 except Exception as e:
254 except Exception as e:
255 raise web.HTTPError(400, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
255 raise web.HTTPError(400, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
256
256
257 # Move the checkpoints
257 # Move the checkpoints
258 old_checkpoints = self.list_checkpoints(old_name, old_path)
258 old_checkpoints = self.list_checkpoints(old_name, old_path)
259 for cp in old_checkpoints:
259 for cp in old_checkpoints:
260 checkpoint_id = cp['checkpoint_id']
260 checkpoint_id = cp['checkpoint_id']
261 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, path)
261 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
262 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, path)
262 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
263 if os.path.isfile(old_cp_path):
263 if os.path.isfile(old_cp_path):
264 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
264 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
265 os.rename(old_cp_path, new_cp_path)
265 os.rename(old_cp_path, new_cp_path)
266
266
267 # Move the .py script
267 # Move the .py script
268 if self.save_script:
268 if self.save_script:
269 os.rename(old_py_path, new_py_path)
269 os.rename(old_py_path, new_py_path)
270
270
271 # Checkpoint-related utilities
271 # Checkpoint-related utilities
272
272
273 def get_checkpoint_path(self, checkpoint_id, name, path='/'):
273 def get_checkpoint_path(self, checkpoint_id, name, path='/'):
274 """find the path to a checkpoint"""
274 """find the path to a checkpoint"""
275 filename = u"{name}-{checkpoint_id}{ext}".format(
275 filename = u"{name}-{checkpoint_id}{ext}".format(
276 name=name,
276 name=name,
277 checkpoint_id=checkpoint_id,
277 checkpoint_id=checkpoint_id,
278 ext=self.filename_ext,
278 ext=self.filename_ext,
279 )
279 )
280 cp_path = os.path.join(path, self.checkpoint_dir, filename)
280 cp_path = os.path.join(path, self.checkpoint_dir, filename)
281 return cp_path
281 return cp_path
282
282
283 def get_checkpoint_model(self, checkpoint_id, name, path='/'):
283 def get_checkpoint_model(self, checkpoint_id, name, path='/'):
284 """construct the info dict for a given checkpoint"""
284 """construct the info dict for a given checkpoint"""
285 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
285 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
286 stats = os.stat(cp_path)
286 stats = os.stat(cp_path)
287 last_modified = tz.utcfromtimestamp(stats.st_mtime)
287 last_modified = tz.utcfromtimestamp(stats.st_mtime)
288 info = dict(
288 info = dict(
289 checkpoint_id = checkpoint_id,
289 checkpoint_id = checkpoint_id,
290 last_modified = last_modified,
290 last_modified = last_modified,
291 )
291 )
292 return info
292 return info
293
293
294 # public checkpoint API
294 # public checkpoint API
295
295
296 def create_checkpoint(self, name, path='/'):
296 def create_checkpoint(self, name, path='/'):
297 """Create a checkpoint from the current state of a notebook"""
297 """Create a checkpoint from the current state of a notebook"""
298 nb_path = self.get_os_path(name, path)
298 nb_path = self.get_os_path(name, path)
299 # only the one checkpoint ID:
299 # only the one checkpoint ID:
300 checkpoint_id = u"checkpoint"
300 checkpoint_id = u"checkpoint"
301 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
301 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
302 self.log.debug("creating checkpoint for notebook %s", name)
302 self.log.debug("creating checkpoint for notebook %s", name)
303 if not os.path.exists(self.checkpoint_dir):
303 if not os.path.exists(self.checkpoint_dir):
304 os.mkdir(self.checkpoint_dir)
304 os.mkdir(self.checkpoint_dir)
305 shutil.copy2(nb_path, cp_path)
305 shutil.copy2(nb_path, cp_path)
306
306
307 # return the checkpoint info
307 # return the checkpoint info
308 return self.get_checkpoint_model(checkpoint_id, name, path)
308 return self.get_checkpoint_model(checkpoint_id, name, path)
309
309
310 def list_checkpoints(self, name, path='/'):
310 def list_checkpoints(self, name, path='/'):
311 """list the checkpoints for a given notebook
311 """list the checkpoints for a given notebook
312
312
313 This notebook manager currently only supports one checkpoint per notebook.
313 This notebook manager currently only supports one checkpoint per notebook.
314 """
314 """
315 checkpoint_id = "checkpoint"
315 checkpoint_id = "checkpoint"
316 path = self.get_checkpoint_path(checkpoint_id, name, path)
316 path = self.get_checkpoint_path(checkpoint_id, name, path)
317 if not os.path.exists(path):
317 if not os.path.exists(path):
318 return []
318 return []
319 else:
319 else:
320 return [self.get_checkpoint_model(checkpoint_id, name, path)]
320 return [self.get_checkpoint_model(checkpoint_id, name, path)]
321
321
322
322
323 def restore_checkpoint(self, checkpoint_id, name, path='/'):
323 def restore_checkpoint(self, checkpoint_id, name, path='/'):
324 """restore a notebook to a checkpointed state"""
324 """restore a notebook to a checkpointed state"""
325 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
325 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
326 nb_path = self.get_os_path(name, path)
326 nb_path = self.get_os_path(name, path)
327 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
327 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
328 if not os.path.isfile(cp_path):
328 if not os.path.isfile(cp_path):
329 self.log.debug("checkpoint file does not exist: %s", cp_path)
329 self.log.debug("checkpoint file does not exist: %s", cp_path)
330 raise web.HTTPError(404,
330 raise web.HTTPError(404,
331 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
331 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
332 )
332 )
333 # ensure notebook is readable (never restore from an unreadable notebook)
333 # ensure notebook is readable (never restore from an unreadable notebook)
334 with file(cp_path, 'r') as f:
334 with file(cp_path, 'r') as f:
335 nb = current.read(f, u'json')
335 nb = current.read(f, u'json')
336 shutil.copy2(cp_path, nb_path)
336 shutil.copy2(cp_path, nb_path)
337 self.log.debug("copying %s -> %s", cp_path, nb_path)
337 self.log.debug("copying %s -> %s", cp_path, nb_path)
338
338
339 def delete_checkpoint(self, checkpoint_id, name, path='/'):
339 def delete_checkpoint(self, checkpoint_id, name, path='/'):
340 """delete a notebook's checkpoint"""
340 """delete a notebook's checkpoint"""
341 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
341 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
342 if not os.path.isfile(cp_path):
342 if not os.path.isfile(cp_path):
343 raise web.HTTPError(404,
343 raise web.HTTPError(404,
344 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
344 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
345 )
345 )
346 self.log.debug("unlinking %s", cp_path)
346 self.log.debug("unlinking %s", cp_path)
347 os.unlink(cp_path)
347 os.unlink(cp_path)
348
348
349 def info_string(self):
349 def info_string(self):
350 return "Serving notebooks from local directory: %s" % self.notebook_dir
350 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -1,169 +1,169 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // SaveWidget
9 // SaveWidget
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var SaveWidget = function (selector) {
17 var SaveWidget = function (selector) {
18 this.selector = selector;
18 this.selector = selector;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 this.style();
21 this.style();
22 this.bind_events();
22 this.bind_events();
23 }
23 }
24 };
24 };
25
25
26
26
27 SaveWidget.prototype.style = function () {
27 SaveWidget.prototype.style = function () {
28 };
28 };
29
29
30
30
31 SaveWidget.prototype.bind_events = function () {
31 SaveWidget.prototype.bind_events = function () {
32 var that = this;
32 var that = this;
33 this.element.find('span#notebook_name').click(function () {
33 this.element.find('span#notebook_name').click(function () {
34 that.rename_notebook();
34 that.rename_notebook();
35 });
35 });
36 this.element.find('span#notebook_name').hover(function () {
36 this.element.find('span#notebook_name').hover(function () {
37 $(this).addClass("ui-state-hover");
37 $(this).addClass("ui-state-hover");
38 }, function () {
38 }, function () {
39 $(this).removeClass("ui-state-hover");
39 $(this).removeClass("ui-state-hover");
40 });
40 });
41 $([IPython.events]).on('notebook_loaded.Notebook', function () {
41 $([IPython.events]).on('notebook_loaded.Notebook', function () {
42 that.update_notebook_name();
42 that.update_notebook_name();
43 that.update_document_title();
43 that.update_document_title();
44 });
44 });
45 $([IPython.events]).on('notebook_saved.Notebook', function () {
45 $([IPython.events]).on('notebook_saved.Notebook', function () {
46 that.update_notebook_name();
46 that.update_notebook_name();
47 that.update_document_title();
47 that.update_document_title();
48 });
48 });
49 $([IPython.events]).on('notebook_renamed.Notebook', function () {
49 $([IPython.events]).on('notebook_renamed.Notebook', function () {
50 that.update_notebook_name();
50 that.update_notebook_name();
51 that.update_document_title();
51 that.update_document_title();
52 that.update_address_bar();
52 that.update_address_bar();
53 });
53 });
54 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
54 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
55 that.set_save_status('Autosave Failed!');
55 that.set_save_status('Autosave Failed!');
56 });
56 });
57 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
57 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
58 that.set_last_checkpoint(data[0]);
58 that.set_last_checkpoint(data[0]);
59 });
59 });
60
60
61 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
61 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
62 that.set_last_checkpoint(data);
62 that.set_last_checkpoint(data);
63 });
63 });
64 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
64 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
65 that.set_autosaved(data.value);
65 that.set_autosaved(data.value);
66 });
66 });
67 };
67 };
68
68
69
69
70 SaveWidget.prototype.rename_notebook = function () {
70 SaveWidget.prototype.rename_notebook = function () {
71 var that = this;
71 var that = this;
72 var dialog = $('<div/>').append(
72 var dialog = $('<div/>').append(
73 $("<p/>").addClass("rename-message")
73 $("<p/>").addClass("rename-message")
74 .html('Enter a new notebook name:')
74 .html('Enter a new notebook name:')
75 ).append(
75 ).append(
76 $("<br/>")
76 $("<br/>")
77 ).append(
77 ).append(
78 $('<input/>').attr('type','text').attr('size','25')
78 $('<input/>').attr('type','text').attr('size','25')
79 .val(IPython.notebook.get_notebook_name())
79 .val(IPython.notebook.get_notebook_name())
80 );
80 );
81 IPython.dialog.modal({
81 IPython.dialog.modal({
82 title: "Rename Notebook",
82 title: "Rename Notebook",
83 body: dialog,
83 body: dialog,
84 buttons : {
84 buttons : {
85 "Cancel": {},
85 "Cancel": {},
86 "OK": {
86 "OK": {
87 class: "btn-primary",
87 class: "btn-primary",
88 click: function () {
88 click: function () {
89 var new_name = $(this).find('input').val();
89 var new_name = $(this).find('input').val();
90 if (!IPython.notebook.test_notebook_name(new_name)) {
90 if (!IPython.notebook.test_notebook_name(new_name)) {
91 $(this).find('.rename-message').html(
91 $(this).find('.rename-message').html(
92 "Invalid notebook name. Notebook names must "+
92 "Invalid notebook name. Notebook names must "+
93 "have 1 or more characters and can contain any characters " +
93 "have 1 or more characters and can contain any characters " +
94 "except :/\\. Please enter a new notebook name:"
94 "except :/\\. Please enter a new notebook name:"
95 );
95 );
96 return false;
96 return false;
97 } else {
97 } else {
98 IPython.notebook.notebook_rename(new_name);
98 IPython.notebook.notebook_rename(new_name);
99 }
99 }
100 }}
100 }}
101 },
101 },
102 open : function (event, ui) {
102 open : function (event, ui) {
103 var that = $(this);
103 var that = $(this);
104 // Upon ENTER, click the OK button.
104 // Upon ENTER, click the OK button.
105 that.find('input[type="text"]').keydown(function (event, ui) {
105 that.find('input[type="text"]').keydown(function (event, ui) {
106 if (event.which === utils.keycodes.ENTER) {
106 if (event.which === utils.keycodes.ENTER) {
107 that.find('.btn-primary').first().click();
107 that.find('.btn-primary').first().click();
108 return false;
108 return false;
109 }
109 }
110 });
110 });
111 that.find('input[type="text"]').focus().select();
111 that.find('input[type="text"]').focus().select();
112 }
112 }
113 });
113 });
114 }
114 }
115
115
116
116
117 SaveWidget.prototype.update_notebook_name = function () {
117 SaveWidget.prototype.update_notebook_name = function () {
118 var nbname = IPython.notebook.get_notebook_name();
118 var nbname = IPython.notebook.get_notebook_name();
119 this.element.find('span#notebook_name').html(nbname);
119 this.element.find('span#notebook_name').html(nbname);
120 };
120 };
121
121
122
122
123 SaveWidget.prototype.update_document_title = function () {
123 SaveWidget.prototype.update_document_title = function () {
124 var nbname = IPython.notebook.get_notebook_name();
124 var nbname = IPython.notebook.get_notebook_name();
125 document.title = nbname;
125 document.title = nbname;
126 };
126 };
127
127
128 SaveWidget.prototype.update_address_bar = function(){
128 SaveWidget.prototype.update_address_bar = function(){
129 var nbname = IPython.notebook.notebook_name;
129 var nbname = IPython.notebook.notebook_name;
130 var path = IPython.notebook.notebookPath();
130 var path = IPython.notebook.notebookPath();
131 var state = {"path": path+nbname}
131 var state = {"path": path+nbname}
132 window.history.replaceState(state, "", "/notebooks/" + path+nbname);
132 window.history.replaceState(state, "", "/notebooks" + path+nbname);
133 }
133 }
134
134
135
135
136 SaveWidget.prototype.set_save_status = function (msg) {
136 SaveWidget.prototype.set_save_status = function (msg) {
137 this.element.find('span#autosave_status').html(msg);
137 this.element.find('span#autosave_status').html(msg);
138 }
138 }
139
139
140 SaveWidget.prototype.set_checkpoint_status = function (msg) {
140 SaveWidget.prototype.set_checkpoint_status = function (msg) {
141 this.element.find('span#checkpoint_status').html(msg);
141 this.element.find('span#checkpoint_status').html(msg);
142 }
142 }
143
143
144 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
144 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
145 if (!checkpoint) {
145 if (!checkpoint) {
146 this.set_checkpoint_status("");
146 this.set_checkpoint_status("");
147 return;
147 return;
148 }
148 }
149 var d = new Date(checkpoint.last_modified);
149 var d = new Date(checkpoint.last_modified);
150 this.set_checkpoint_status(
150 this.set_checkpoint_status(
151 "Last Checkpoint: " + d.format('mmm dd HH:MM')
151 "Last Checkpoint: " + d.format('mmm dd HH:MM')
152 );
152 );
153 }
153 }
154
154
155 SaveWidget.prototype.set_autosaved = function (dirty) {
155 SaveWidget.prototype.set_autosaved = function (dirty) {
156 if (dirty) {
156 if (dirty) {
157 this.set_save_status("(unsaved changes)");
157 this.set_save_status("(unsaved changes)");
158 } else {
158 } else {
159 this.set_save_status("(autosaved)");
159 this.set_save_status("(autosaved)");
160 }
160 }
161 };
161 };
162
162
163
163
164 IPython.SaveWidget = SaveWidget;
164 IPython.SaveWidget = SaveWidget;
165
165
166 return IPython;
166 return IPython;
167
167
168 }(IPython));
168 }(IPython));
169
169
General Comments 0
You need to be logged in to leave comments. Login now