When saving a note, auto-delete any files that used to be linked from it but no longer are. Still need unit tests.
This commit is contained in:
parent
7b8f6bd6e5
commit
65ce915755
|
@ -1,16 +0,0 @@
|
|||
import cherrypy
|
||||
|
||||
|
||||
def async( method ):
|
||||
"""
|
||||
A decorator for a generator method that causes it to be invoked asynchronously. In other words,
|
||||
whenever a generator method decorated by this decorator is called, its generator is added to
|
||||
the scheduler for later execution.
|
||||
|
||||
This decorator expects a self.scheduler member containing the scheduler to use.
|
||||
"""
|
||||
def schedule( self, *args, **kwargs ):
|
||||
thread = method( self, *args, **kwargs )
|
||||
self.scheduler.add( thread )
|
||||
|
||||
return schedule
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import re
|
||||
import cgi
|
||||
import time
|
||||
import tempfile
|
||||
|
@ -197,6 +198,8 @@ cherrypy._cpcgifs.FieldStorage = FieldStorage
|
|||
|
||||
|
||||
class Files( object ):
|
||||
FILE_LINK_PATTERN = re.compile( u'<a\s+href="[^"]*/files/download\?file_id=([^"]+)">', re.IGNORECASE )
|
||||
|
||||
"""
|
||||
Controller for dealing with uploaded files, corresponding to the "/files" URL.
|
||||
"""
|
||||
|
@ -527,3 +530,29 @@ class Files( object ):
|
|||
self.__database.save( db_file )
|
||||
|
||||
return dict()
|
||||
|
||||
def purge_unused( self, note ):
|
||||
"""
|
||||
Delete files that were linked from the given note but no longer are.
|
||||
|
||||
@type note: model.Note
|
||||
@param note: note to search for file links
|
||||
"""
|
||||
# load metadata for all files with the given note's note_id
|
||||
files = self.__database.select_many( File, File.sql_load_note_files( note.object_id ) )
|
||||
files_to_delete = dict( [ ( db_file.object_id, db_file ) for db_file in files ] )
|
||||
|
||||
# search through the note's contents for current links to files
|
||||
for match in self.FILE_LINK_PATTERN.finditer( note.contents ):
|
||||
file_id = match.groups( 0 )[ 0 ]
|
||||
|
||||
# we've found a link for file_id, so don't delete that file
|
||||
files_to_delete.pop( file_id, None )
|
||||
|
||||
# for each file to delete, delete its metadata from the database and its data from the
|
||||
# filesystem
|
||||
for ( file_id, db_file ) in files_to_delete.items():
|
||||
self.__database.execute( db_file.sql_delete(), commit = False )
|
||||
os.remove( Upload_file.make_server_filename( file_id ) )
|
||||
|
||||
self.__database.commit()
|
||||
|
|
|
@ -36,7 +36,7 @@ class Notebooks( object ):
|
|||
"""
|
||||
Controller for dealing with notebooks and their notes, corresponding to the "/notebooks" URL.
|
||||
"""
|
||||
def __init__( self, database, users ):
|
||||
def __init__( self, database, users, files ):
|
||||
"""
|
||||
Create a new Notebooks object.
|
||||
|
||||
|
@ -44,11 +44,14 @@ class Notebooks( object ):
|
|||
@param database: database that notebooks are stored in
|
||||
@type users: controller.Users
|
||||
@param users: controller for all users, used here for updating storage utilization
|
||||
@rtype: Notebooks
|
||||
@type files: controller.Files
|
||||
@param files: controller for all uploaded files, used here for deleting files that are no longer
|
||||
referenced within saved notes
|
||||
@return: newly constructed Notebooks
|
||||
"""
|
||||
self.__database = database
|
||||
self.__users = users
|
||||
self.__files = files
|
||||
|
||||
@expose( view = Main_page )
|
||||
@strongly_expire
|
||||
|
@ -509,6 +512,8 @@ class Notebooks( object ):
|
|||
|
||||
new_revision = User_revision( note.revision, note.user_id, user.username )
|
||||
|
||||
self.__files.purge_unused( note )
|
||||
|
||||
return new_revision
|
||||
|
||||
# if the note is already in the given notebook, load it and update it
|
||||
|
|
|
@ -43,8 +43,8 @@ class Root( object ):
|
|||
settings[ u"global" ].get( u"luminotes.payment_email", u"" ),
|
||||
settings[ u"global" ].get( u"luminotes.rate_plans", [] ),
|
||||
)
|
||||
self.__notebooks = Notebooks( database, self.__users )
|
||||
self.__files = Files( database, self.__users )
|
||||
self.__notebooks = Notebooks( database, self.__users, self.__files )
|
||||
self.__suppress_exceptions = suppress_exceptions # used for unit tests
|
||||
|
||||
@expose( Main_page )
|
||||
|
|
|
@ -98,6 +98,18 @@ class File( Persistent ):
|
|||
def sql_delete( self ):
|
||||
return "delete from file where id = %s;" % quote( self.object_id )
|
||||
|
||||
@staticmethod
|
||||
def sql_load_note_files( note_id ):
|
||||
return \
|
||||
"""
|
||||
select
|
||||
file.id, file.revision, file.notebook_id, file.note_id, file.filename, file.size_bytes, file.content_type
|
||||
from
|
||||
file
|
||||
where
|
||||
file.note_id = %s;
|
||||
""" % quote( note_id )
|
||||
|
||||
def to_dict( self ):
|
||||
d = Persistent.to_dict( self )
|
||||
d.update( dict(
|
||||
|
|
|
@ -8,3 +8,4 @@ create table file (
|
|||
content_type text
|
||||
);
|
||||
alter table file add primary key ( id );
|
||||
create index file_note_id_index on file using btree ( note_id );
|
||||
|
|
|
@ -234,6 +234,13 @@ ALTER TABLE ONLY user_notebook
|
|||
ADD CONSTRAINT user_notebook_pkey PRIMARY KEY (user_id, notebook_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: file_note_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX file_note_id_index ON file USING btree (note_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: luminotes_user_email_address_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
|
||||
--
|
||||
|
|
Reference in New Issue