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