Browse Source

When saving a note, auto-delete any files that used to be linked from it but no longer are. Still need unit tests.

Dan Helfman 10 years ago
parent
commit
65ce915755
7 changed files with 57 additions and 19 deletions
  1. 0
    16
      controller/Async.py
  2. 29
    0
      controller/Files.py
  3. 7
    2
      controller/Notebooks.py
  4. 1
    1
      controller/Root.py
  5. 12
    0
      model/File.py
  6. 1
    0
      model/delta/1.2.0.sql
  7. 7
    0
      model/schema.sql

+ 0
- 16
controller/Async.py View File

@@ -1,16 +0,0 @@
1
-import cherrypy
2
-
3
-
4
-def async( method ):
5
-  """
6
-  A decorator for a generator method that causes it to be invoked asynchronously. In other words,
7
-  whenever a generator method decorated by this decorator is called, its generator is added to
8
-  the scheduler for later execution.
9
-
10
-  This decorator expects a self.scheduler member containing the scheduler to use.
11
-  """
12
-  def schedule( self, *args, **kwargs ):
13
-    thread = method( self, *args, **kwargs )
14
-    self.scheduler.add( thread )
15
-  
16
-  return schedule

+ 29
- 0
controller/Files.py View File

@@ -1,4 +1,5 @@
1 1
 import os
2
+import re
2 3
 import cgi
3 4
 import time
4 5
 import tempfile
@@ -197,6 +198,8 @@ cherrypy._cpcgifs.FieldStorage = FieldStorage
197 198
 
198 199
 
199 200
 class Files( object ):
201
+  FILE_LINK_PATTERN = re.compile( u'<a\s+href="[^"]*/files/download\?file_id=([^"]+)">', re.IGNORECASE )
202
+
200 203
   """
201 204
   Controller for dealing with uploaded files, corresponding to the "/files" URL.
202 205
   """
@@ -527,3 +530,29 @@ class Files( object ):
527 530
     self.__database.save( db_file )
528 531
 
529 532
     return dict()
533
+
534
+  def purge_unused( self, note ):
535
+    """
536
+    Delete files that were linked from the given note but no longer are.
537
+
538
+    @type note: model.Note
539
+    @param note: note to search for file links
540
+    """
541
+    # load metadata for all files with the given note's note_id 
542
+    files = self.__database.select_many( File, File.sql_load_note_files( note.object_id ) )
543
+    files_to_delete = dict( [ ( db_file.object_id, db_file ) for db_file in files ] )
544
+
545
+    # search through the note's contents for current links to files
546
+    for match in self.FILE_LINK_PATTERN.finditer( note.contents ):
547
+      file_id = match.groups( 0 )[ 0 ]
548
+
549
+      # we've found a link for file_id, so don't delete that file
550
+      files_to_delete.pop( file_id, None )
551
+
552
+    # for each file to delete, delete its metadata from the database and its data from the
553
+    # filesystem
554
+    for ( file_id, db_file ) in files_to_delete.items():
555
+      self.__database.execute( db_file.sql_delete(), commit = False )
556
+      os.remove( Upload_file.make_server_filename( file_id ) )
557
+
558
+    self.__database.commit()

+ 7
- 2
controller/Notebooks.py View File

@@ -36,7 +36,7 @@ class Notebooks( object ):
36 36
   """
37 37
   Controller for dealing with notebooks and their notes, corresponding to the "/notebooks" URL.
38 38
   """
39
-  def __init__( self, database, users ):
39
+  def __init__( self, database, users, files ):
40 40
     """
41 41
     Create a new Notebooks object.
42 42
 
@@ -44,11 +44,14 @@ class Notebooks( object ):
44 44
     @param database: database that notebooks are stored in
45 45
     @type users: controller.Users
46 46
     @param users: controller for all users, used here for updating storage utilization
47
-    @rtype: Notebooks
47
+    @type files: controller.Files
48
+    @param files: controller for all uploaded files, used here for deleting files that are no longer
49
+                  referenced within saved notes
48 50
     @return: newly constructed Notebooks
49 51
     """
50 52
     self.__database = database
51 53
     self.__users = users
54
+    self.__files = files
52 55
 
53 56
   @expose( view = Main_page )
54 57
   @strongly_expire
@@ -509,6 +512,8 @@ class Notebooks( object ):
509 512
 
510 513
         new_revision = User_revision( note.revision, note.user_id, user.username )
511 514
 
515
+        self.__files.purge_unused( note )
516
+
512 517
       return new_revision
513 518
 
514 519
     # if the note is already in the given notebook, load it and update it

+ 1
- 1
controller/Root.py View File

@@ -43,8 +43,8 @@ class Root( object ):
43 43
       settings[ u"global" ].get( u"luminotes.payment_email", u"" ),
44 44
       settings[ u"global" ].get( u"luminotes.rate_plans", [] ),
45 45
     )
46
-    self.__notebooks = Notebooks( database, self.__users )
47 46
     self.__files = Files( database, self.__users )
47
+    self.__notebooks = Notebooks( database, self.__users, self.__files )
48 48
     self.__suppress_exceptions = suppress_exceptions # used for unit tests
49 49
 
50 50
   @expose( Main_page )

+ 12
- 0
model/File.py View File

@@ -98,6 +98,18 @@ class File( Persistent ):
98 98
   def sql_delete( self ):
99 99
     return "delete from file where id = %s;" % quote( self.object_id )
100 100
 
101
+  @staticmethod
102
+  def sql_load_note_files( note_id ):
103
+    return \
104
+      """
105
+      select
106
+        file.id, file.revision, file.notebook_id, file.note_id, file.filename, file.size_bytes, file.content_type
107
+      from
108
+        file
109
+      where
110
+        file.note_id = %s;
111
+      """ % quote( note_id )
112
+
101 113
   def to_dict( self ):
102 114
     d = Persistent.to_dict( self )
103 115
     d.update( dict(

+ 1
- 0
model/delta/1.2.0.sql View File

@@ -8,3 +8,4 @@ create table file (
8 8
   content_type text
9 9
 );
10 10
 alter table file add primary key ( id );
11
+create index file_note_id_index on file using btree ( note_id );

+ 7
- 0
model/schema.sql View File

@@ -234,6 +234,13 @@ ALTER TABLE ONLY user_notebook
234 234
     ADD CONSTRAINT user_notebook_pkey PRIMARY KEY (user_id, notebook_id);
235 235
 
236 236
 
237
+--
238
+-- Name: file_note_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace: 
239
+--
240
+
241
+CREATE INDEX file_note_id_index ON file USING btree (note_id);
242
+
243
+
237 244
 --
238 245
 -- Name: luminotes_user_email_address_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace: 
239 246
 --

Loading…
Cancel
Save