Index: openacs-4/packages/notifications/notifications.info =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/notifications.info,v diff -u -r1.1 -r1.2 --- openacs-4/packages/notifications/notifications.info 24 May 2002 20:42:42 -0000 1.1 +++ openacs-4/packages/notifications/notifications.info 29 May 2002 05:12:01 -0000 1.2 @@ -1,7 +1,7 @@ - + Notifications Notifications f @@ -15,22 +15,37 @@ Ben Adida Notification Management - + + + + + + + + + + + + + + + + Index: openacs-4/packages/notifications/sql/oracle/notifications-core-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/sql/oracle/notifications-core-create.sql,v diff -u -r1.1 -r1.2 --- openacs-4/packages/notifications/sql/oracle/notifications-core-create.sql 24 May 2002 20:42:42 -0000 1.1 +++ openacs-4/packages/notifications/sql/oracle/notifications-core-create.sql 29 May 2002 05:12:01 -0000 1.2 @@ -120,6 +120,7 @@ -- this is to allow responses to notifications response_id integer constraint notif_reponse_id_fk references acs_objects(object_id), + notif_subject varchar(100), notif_text varchar(4000), notif_html varchar(4000) ); Index: openacs-4/packages/notifications/sql/oracle/notifications-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/sql/oracle/notifications-create.sql,v diff -u -r1.1 -r1.2 --- openacs-4/packages/notifications/sql/oracle/notifications-create.sql 24 May 2002 20:42:42 -0000 1.1 +++ openacs-4/packages/notifications/sql/oracle/notifications-create.sql 29 May 2002 05:12:01 -0000 1.2 @@ -14,3 +14,5 @@ -- the service contracts will eventually be created -- @ notifications-interval-sc-create.sql -- @ notifications-delivery-sc-create.sql + +@notifications-init.sql Index: openacs-4/packages/notifications/sql/oracle/notifications-init.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/sql/oracle/notifications-init.sql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/sql/oracle/notifications-init.sql 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,45 @@ + +-- +-- The Notifications Package +-- +-- ben@openforce.net +-- Copyright OpenForce, 2002. +-- +-- GNU GPL v2 +-- + +-- initialize some stuff +declare + v_foo integer; +begin + v_foo:= notification_interval.new ( + name => 'daily', + n_seconds => 3600 * 24, + creation_user => NULL, + creation_ip => NULL + ); + + v_foo:= notification_interval.new ( + name => 'hourly', + n_seconds => 3600, + creation_user => NULL, + creation_ip => NULL + ); + + v_foo:= notification_interval.new ( + name => 'instant', + n_seconds => 0, + creation_user => NULL, + creation_ip => NULL + ); + + v_foo:= notification_delivery_method.new ( + short_name => 'email', + pretty_name => 'Email', + creation_user => NULL, + creation_ip => NULL + ); + +end; +/ +show errors Index: openacs-4/packages/notifications/sql/oracle/notifications-package-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/sql/oracle/notifications-package-create.sql,v diff -u -r1.1 -r1.2 --- openacs-4/packages/notifications/sql/oracle/notifications-package-create.sql 24 May 2002 20:42:42 -0000 1.1 +++ openacs-4/packages/notifications/sql/oracle/notifications-package-create.sql 29 May 2002 05:12:01 -0000 1.2 @@ -235,6 +235,10 @@ procedure delete ( request_id in notification_requests.request_id%TYPE default null ); + + procedure delete_all ( + object_id in notification_requests.object_id%TYPE default null + ); end notification_request; / show errors @@ -283,6 +287,19 @@ acs_object.delete(request_id); end delete; + procedure delete_all ( + object_id in notification_requests.object_id%TYPE default null + ) + is + v_request notification_requests%ROWTYPE; + begin + for v_request in + (select request_id from notification_requests where object_id= delete_all.object_id) + LOOP + notification_request.delete(v_request.request_id); + END LOOP; + end delete_all; + end notification_request; / show errors @@ -326,6 +343,7 @@ object_id in notifications.object_id%TYPE, notif_date in notifications.notif_date%TYPE default sysdate, response_id in notifications.response_id%TYPE default null, + notif_subject in notifications.notif_subject%TYPE default null, notif_text in notifications.notif_text%TYPE default null, notif_html in notifications.notif_html%TYPE default null, creation_date in acs_objects.creation_date%TYPE default sysdate, @@ -346,9 +364,9 @@ ); insert into notifications - (notification_id, type_id, object_id, notif_date, response_id, notif_text, notif_html) + (notification_id, type_id, object_id, notif_date, response_id, notif_subject, notif_text, notif_html) values - (v_notification_id, type_id, object_id, notif_date, response_id, notif_text, notif_html); + (v_notification_id, type_id, object_id, notif_date, response_id, notif_subject, notif_text, notif_html); return v_notification_id; end new; Index: openacs-4/packages/notifications/tcl/notification-display-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/notification-display-procs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/tcl/notification-display-procs.tcl 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,42 @@ +ad_library { + + Notifications Display Procs + + @creation-date 2002-05-24 + @author Ben Adida + @cvs-id $Id: notification-display-procs.tcl,v 1.1 2002/05/29 05:12:01 ben Exp $ + +} + +namespace eval notification::display { + + ad_proc -public request_widget { + {-type:required} + {-object_id:required} + {-pretty_name:required} + {-url:required} + } { + Produce a widget for requesting notifications + } { + set user_id [ad_conn user_id] + + # Get the type id + set type_id [notification::type::get_type_id -short_name $type] + + # Check if subscribed + set request_id [notification::request::get_request_id -type_id $type_id -object_id $object_id -user_id $user_id] + + set root_path [apm_package_url_from_key [notification::package_key]] + set encoded_stuff "pretty_name=[ns_urlencode $pretty_name]&return_url=[ns_urlencode $url]" + + if {![empty_string_p $request_id]} { + set sub_url "${root_path}request-delete?request_id=$request_id&$encoded_stuff" + set sub_chunk "You have requested notification for $pretty_name. You may unsubscribe." + } else { + set sub_url "${root_path}request-new?type_id=$type_id&user_id=$user_id&object_id=$object_id&$encoded_stuff" + set sub_chunk "You may request notification for $pretty_name." + } + + return "\[ $sub_chunk \]" + } +} Index: openacs-4/packages/notifications/tcl/notification-procs-oracle.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/notification-procs-oracle.xql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/tcl/notification-procs-oracle.xql 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,13 @@ + + + oracle8.1.6 + + + +declare begin + notification.delete(:notification_id); +end; + + + + Index: openacs-4/packages/notifications/tcl/notification-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/notification-procs.tcl,v diff -u -r1.1 -r1.2 --- openacs-4/packages/notifications/tcl/notification-procs.tcl 24 May 2002 20:42:42 -0000 1.1 +++ openacs-4/packages/notifications/tcl/notification-procs.tcl 29 May 2002 05:12:01 -0000 1.2 @@ -10,13 +10,40 @@ namespace eval notification { + ad_proc -public package_key {} { + return "notifications" + } + + ad_proc -public get_all_intervals {} { + return [db_list_of_lists select_all_intervals {}] + } + + ad_proc -public get_intervals { + {-type_id:required} + } { + return [db_list_of_lists select_intervals {}] + } + + ad_proc -public get_delivery_methods { + {-type_id:required} + } { + return [db_list_of_lists select_delivery_methods {}] + } + ad_proc -public new { + {-notification_id ""} + {-type_id:required} + {-object_id:required} + {-response_id ""} + {-notif_subject ""} + {-notif_text ""} + {-notif_html ""} } { create a new notification } { # Set up the vars set extra_vars [ns_set create] - oacs_util::vars_to_ns_set -ns_set $extra_vars -var_list {} + oacs_util::vars_to_ns_set -ns_set $extra_vars -var_list {notification_id type_id object_id response_id notif_subject notif_text notif_html} # Create the request set notification_id [package_instantiate_object -extra_vars $extra_vars notification] @@ -29,9 +56,13 @@ } { delete a notification } { - # do the delete - # FIXME: implement this - db_exec_plsql delete_notification {} + db_transaction { + # Remove the mappings + db_dml delete_mappings {} + + # do the delete + db_exec_plsql delete_notification {} + } } ad_proc -public mark_sent { Index: openacs-4/packages/notifications/tcl/notification-procs.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/notification-procs.xql,v diff -u -r1.1 -r1.2 --- openacs-4/packages/notifications/tcl/notification-procs.xql 24 May 2002 20:42:42 -0000 1.1 +++ openacs-4/packages/notifications/tcl/notification-procs.xql 29 May 2002 05:12:01 -0000 1.2 @@ -1,11 +1,48 @@ + + +select name, interval_id from +notification_intervals +order by n_seconds + + + + + +select name, notification_intervals.interval_id from +notification_intervals, notification_types_intervals +where +notification_intervals.interval_id = notification_types_intervals.interval_id +and type_id= :type_id +order by n_seconds + + + + + +select pretty_name, notification_delivery_methods.delivery_method_id +from notification_delivery_methods, notification_types_del_methods +where +notification_delivery_methods.delivery_method_id = notification_types_del_methods.delivery_method_id +and type_id= :type_id +order by pretty_name + + + + + +delete from notification_user_map +where notification_id= :notification_id + + + insert into notification_user_map (notification_id, user_id, sent_date) values -(:notification_id, :user_id, sysdate()) +(:notification_id, :user_id, sysdate) Index: openacs-4/packages/notifications/tcl/notification-request-procs-oracle.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/notification-request-procs-oracle.xql,v diff -u -r1.1 -r1.2 --- openacs-4/packages/notifications/tcl/notification-request-procs-oracle.xql 24 May 2002 20:42:42 -0000 1.1 +++ openacs-4/packages/notifications/tcl/notification-request-procs-oracle.xql 29 May 2002 05:12:01 -0000 1.2 @@ -10,4 +10,12 @@ + + +declare begin + notification_request.delete_all(object_id => :object_id); +end; + + + Index: openacs-4/packages/notifications/tcl/notification-request-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/notification-request-procs.tcl,v diff -u -r1.1 -r1.2 --- openacs-4/packages/notifications/tcl/notification-request-procs.tcl 24 May 2002 20:42:42 -0000 1.1 +++ openacs-4/packages/notifications/tcl/notification-request-procs.tcl 29 May 2002 05:12:01 -0000 1.2 @@ -31,6 +31,16 @@ return $request_id } + ad_proc -public get_request_id { + {-type_id:required} + {-object_id:required} + {-user_id:required} + } { + Checks if a particular notification request exists + } { + return [db_string select_request_id {} -default {}] + } + ad_proc -public delete { {-request_id:required} } { @@ -39,5 +49,14 @@ # do the delete db_exec_plsql delete_request {} } - + + ad_proc -public delete_all { + {-object_id:required} + } { + remove all requests for a particular object ID + usually because the object is getting deleted + } { + # Do it + db_exec_plsql delete_all_requests {} + } } Index: openacs-4/packages/notifications/tcl/notification-request-procs.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/notification-request-procs.xql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/tcl/notification-request-procs.xql 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,11 @@ + + + + + +select request_id from notification_requests where +type_id= :type_id and user_id= :user_id and object_id= :object_id + + + + Index: openacs-4/packages/notifications/tcl/notifications-security-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/notifications-security-procs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/tcl/notifications-security-procs.tcl 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,42 @@ +ad_library { + + Notifications Security Library + + @creation-date 2002-05-27 + @author Ben Adida + @cvs-id $Id: notifications-security-procs.tcl,v 1.1 2002/05/29 05:12:01 ben Exp $ + +} + +namespace eval notification::security { + + ad_proc -public can_notify_object_p { + {-user_id ""} + {-object_id:required} + } { + # HACK + return 1 + } + + ad_proc -public require_notify_object { + {-user_id ""} + {-object_id:required} + } { + } + + ad_proc -public can_admin_request_p { + {-user_id ""} + {-request_id:required} + } { + # hack + return 1 + } + + ad_proc -public require_admin_request { + {-user_id ""} + {-request_id:required} + } { + } + + +} Index: openacs-4/packages/notifications/tcl/sweep-init.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/sweep-init.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/tcl/sweep-init.tcl 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,17 @@ +ad_library { + + notifications init - sets up scheduled procs + + @cvs-id $Id: sweep-init.tcl,v 1.1 2002/05/29 05:12:01 ben Exp $ + @author Ben Adida (ben@openforce) + @date 2002-05-27 + +} + +# Hack for now to test immediate deliveries +# FIXME +ad_schedule_proc -thread t 60 notification::sweep::cleanup_notifications + +foreach interval [notification::get_all_intervals] { + ad_schedule_proc -thread t 120 notification::sweep::sweep_notifications -interval_id [lindex $interval 1] +} Index: openacs-4/packages/notifications/tcl/sweep-procs-oracle.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/Attic/sweep-procs-oracle.xql,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/tcl/sweep-procs-oracle.xql 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,30 @@ + + + oracle8.1.6 + + + +select notification_id +from notifications where not exists +(select notifications.notification_id +from notifications, notification_requests, notification_user_map +where notifications.type_id = notification_requests.type_id +and notifications.object_id = notification_requests.object_id +and notifications.notification_id = notification_user_map.notification_id (+) +and sent_date is NULL) + + + + + +select notifications.notification_id, notif_subject, notif_text, notif_html, notification_requests.user_id +from notifications, notification_requests, notification_user_map +where notifications.type_id = notification_requests.type_id +and interval_id = :interval_id +and notifications.object_id = notification_requests.object_id +and notifications.notification_id = notification_user_map.notification_id (+) +and sent_date is NULL + + + + Index: openacs-4/packages/notifications/tcl/sweep-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/sweep-procs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/tcl/sweep-procs.tcl 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,82 @@ +ad_library { + + Notification Sweeps + + @creation-date 2002-05-27 + @author Ben Adida + @cvs-id $Id: sweep-procs.tcl,v 1.1 2002/05/29 05:12:01 ben Exp $ + +} + +namespace eval notification::sweep { + + ad_proc -public schedule_all {} { + This schedules all the notification procs + } { + } + + ad_proc -public send_one { + {-user_id:required} + {-subject:required} + {-content:required} + {-response_id:required} + {-delivery_method_id:required} + } { + hack currently send only by email + # FIXME + } { + # Get email + set email [cc_email_from_party $user_id] + + acs_mail_lite::send -to_addr $email -from_addr "notifications@openforce.biz" \ + -subject $subject \ + -body $content + } + + ad_proc -public cleanup_notifications {} { + Clean up the notifications that are done + } { + # Get the list of the ones to kill + set notification_id_list [db_list select_notification_ids {}] + + # Kill them + foreach notification_id $notification_id_list { + notification::delete -notification_id $notification_id + } + } + + ad_proc -public sweep_notifications { + {-interval_id:required} + {-batched_p 0} + } { + This sweeps for notifications in a particular interval + } { + # Look for notifications joined against the requests they may match with the right interval_id + # order it by user_id + # make sure the users have not yet received this notification with outer join + # on the mapping table and a null check + set notifications [db_list_of_ns_sets select_notifications {}] + + foreach notif $notifications { + # If not batched, just send out and mark it + if {!$batched_p} { + db_transaction { + # Send it + send_one -user_id [ns_set get $notif user_id] \ + -subject "[Notification]: [ns_set get $notif notif_subject]" \ + -content [ns_set get $notif notif_text] \ + -response_id [ns_set get $notif response_id] \ + -delivery_method_id [ns_set get $notif delivery_method_id] + + # Markt it as sent + notification::mark_sent -notification_id [ns_set get $notif notification_id] \ + -user_id [ns_set get $notif user_id] + } + } else { + # It's batched, we're not handling this one yet + ns_log Notice "Notifcations: Batched Request not handled" + } + } + } + +} Index: openacs-4/packages/notifications/www/master.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/www/Attic/master.adp,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/www/master.adp 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,3 @@ + +@title@ + Index: openacs-4/packages/notifications/www/request-delete.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/www/request-delete.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/www/request-delete.tcl 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,21 @@ + +ad_page_contract { + + Remove a notification request + + @author Ben Adida (ben@openforce) + @creation-date 2002-05-24 + @cvs-id $Id: request-delete.tcl,v 1.1 2002/05/29 05:12:01 ben Exp $ +} { + request_id:integer,notnull + return_url +} + +# Security Check +notification::security::require_admin_request -request_id $request_id + +# Actually Delete +notification::request::delete -request_id $request_id + +# Redirect +ad_returnredirect $return_url Index: openacs-4/packages/notifications/www/request-new-2.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/www/request-new-2.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/www/request-new-2.tcl 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,30 @@ + +ad_page_contract { + + Request a new notification + + @author Ben Adida (ben@openforce) + @creation-date 2002-05-24 + @cvs-id $Id: request-new-2.tcl,v 1.1 2002/05/29 05:12:01 ben Exp $ +} { + type_id:integer,notnull + object_id:integer,notnull + return_url +} + +set user_id [ad_conn user_id] + +# Check that the object can be subcribed to +notification::security::require_notify_object -object_id $object_id + +# Add the request +notification::request::new \ + -type_id $type_id \ + -user_id $user_id \ + -object_id $object_id \ + -interval_id $interval_id \ + -delivery_method_id $delivery_method_id + +ad_returnredirect $return_url + + Index: openacs-4/packages/notifications/www/request-new.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/www/request-new.adp,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/www/request-new.adp 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,5 @@ + +New Notification Request for @pretty_name@ + + + Index: openacs-4/packages/notifications/www/request-new.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/www/request-new.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/notifications/www/request-new.tcl 29 May 2002 05:12:01 -0000 1.1 @@ -0,0 +1,55 @@ + +ad_page_contract { + + Request a new notification - Ask for more stuff + + @author Ben Adida (ben@openforce) + @creation-date 2002-05-24 + @cvs-id $Id: request-new.tcl,v 1.1 2002/05/29 05:12:01 ben Exp $ +} { + type_id:integer,notnull + object_id:integer,notnull + {pretty_name ""} + return_url +} + +set user_id [ad_conn user_id] + +# Check that the object can be subcribed to +notification::security::require_notify_object -object_id $object_id + +form create request + +element create request type_id \ + -label "Type ID" -datatype integer -widget hidden + +element create request object_id \ + -label "Object ID" -datatype integer -widget hidden + +element create request return_url \ + -label "Return URL" -datatype text -widget hidden + +element create request interval_id \ + -label "Notification Interval" -datatype integer -widget select -options [notification::get_intervals -type_id $type_id] + +element create request delivery_method_id \ + -label "Delivery Method" -datatype integer -widget select -options [notification::get_delivery_methods -type_id $type_id] + +if {[form is_valid request]} { + template::form get_values request type_id object_id return_url interval_id delivery_method_id + + # Add the request + notification::request::new \ + -type_id $type_id \ + -user_id $user_id \ + -object_id $object_id \ + -interval_id $interval_id \ + -delivery_method_id $delivery_method_id + + ad_returnredirect $return_url + ad_script_abort +} + +element set_properties request type_id -value $type_id +element set_properties request object_id -value $object_id +element set_properties request return_url -value $return_url Index: openacs-4/packages/notifications/www/test.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/www/Attic/test.tcl,v diff -u -r1.1 -r1.2 --- openacs-4/packages/notifications/www/test.tcl 24 May 2002 20:42:42 -0000 1.1 +++ openacs-4/packages/notifications/www/test.tcl 29 May 2002 05:12:01 -0000 1.2 @@ -1,24 +1,7 @@ -# Create a notification type -db_transaction { - set interval_id [db_exec_plsql new_interval "declare begin - :1 := notification_interval.new (name => 'hourly' , n_seconds => 3600, creation_user => NULL, creation_ip => NULL); - end; - "] - - set delivery_method_id [db_exec_plsql new_deliv_method "declare begin - :1 := notification_delivery_method.new (short_name => 'email', pretty_name => 'Email', creation_user => NULL, creation_ip => NULL); - end; - "] - - set type_id [notification::type::new -short_name "test" -pretty_name "Test Notification" -description "foobar"] - - # enable both - notification::type::interval_enable -type_id $type_id -interval_id $interval_id - notification::type::delivery_method_enable -type_id $type_id -delivery_method_id $delivery_method_id - - set request_id [notification::request::new -type_id $type_id -user_id 2394 -interval_id $interval_id -delivery_method_id $delivery_method_id -object_id 2394] +# Send it all out +foreach interval [notification::get_all_intervals] { + notification::sweep::sweep_notifications -interval_id [lindex $interval 1] } -doc_body_append $request_id - +doc_body_append "done"