• Publicity: Public Only All

content-revision-procs.tcl

CRUD procedures for content revisions

Location:
packages/acs-content-repository/tcl/content-revision-procs.tcl
Created:
2004-06-04
Author:
Dave Bauer <dave@thedesignexperience.org>
CVS Identification:
$Id: content-revision-procs.tcl,v 1.36.2.9 2022/09/12 15:30:42 antoniop Exp $

Procedures in this file

Detailed information

content::revision::content_copy (public)

 content::revision::content_copy -revision_id revision_id \
    [ -revision_id_dest revision_id_dest ]
Switches:
-revision_id
(required)
-revision_id_dest
(optional)

Partial Call Graph (max 5 caller/called nodes):
%3 package_exec_plsql package_exec_plsql (public) content::revision::content_copy content::revision::content_copy content::revision::content_copy->package_exec_plsql

Testcases:
No testcase defined.

content::revision::copy (public)

 content::revision::copy -revision_id revision_id [ -copy_id copy_id ] \
    [ -target_item_id target_item_id ] \
    [ -creation_user creation_user ] [ -creation_ip creation_ip ]
Switches:
-revision_id
(required)
-copy_id
(optional)
-target_item_id
(optional)
-creation_user
(optional)
-creation_ip
(optional)
Returns:
NUMBER(38)

Partial Call Graph (max 5 caller/called nodes):
%3 fs::file_copy fs::file_copy (public) content::revision::copy content::revision::copy fs::file_copy->content::revision::copy package_exec_plsql package_exec_plsql (public) content::revision::copy->package_exec_plsql

Testcases:
No testcase defined.

content::revision::delete (public)

 content::revision::delete -revision_id revision_id
Switches:
-revision_id
(required)

Partial Call Graph (max 5 caller/called nodes):
%3 package_exec_plsql package_exec_plsql (public) content::revision::delete content::revision::delete content::revision::delete->package_exec_plsql

Testcases:
No testcase defined.

content::revision::export_to_filesystem (public)

 content::revision::export_to_filesystem -revision_id revision_id \
    -storage_type storage_type -filename filename

Export the content of the provided revision to the named file in the filesystem.

Switches:
-revision_id
(required)
-storage_type
(required)
-filename
(required)

Partial Call Graph (max 5 caller/called nodes):
%3 fs::publish_versioned_object_to_file_system fs::publish_versioned_object_to_file_system (public) content::revision::export_to_filesystem content::revision::export_to_filesystem fs::publish_versioned_object_to_file_system->content::revision::export_to_filesystem

Testcases:
No testcase defined.

content::revision::export_xml (public)

 content::revision::export_xml -revision_id revision_id
Switches:
-revision_id
(required)
Returns:
NUMBER(38)

Partial Call Graph (max 5 caller/called nodes):
%3 package_exec_plsql package_exec_plsql (public) content::revision::export_xml content::revision::export_xml content::revision::export_xml->package_exec_plsql

Testcases:
No testcase defined.

content::revision::get_cr_file_path (public)

 content::revision::get_cr_file_path [ -revision_id revision_id ]

Get the path to content in the filesystem

Switches:
-revision_id
(optional)
Returns:
path to filesystem stored revision content
Author:
Dave Bauer <dave@solutiongrove.com>
Created:
2006-08-27

Partial Call Graph (max 5 caller/called nodes):
%3 test_apm_tarballs apm_tarballs (test acs-tcl) content::revision::get_cr_file_path content::revision::get_cr_file_path test_apm_tarballs->content::revision::get_cr_file_path test_fs_upload_a_notmpfile fs_upload_a_notmpfile (test file-storage) test_fs_upload_a_notmpfile->content::revision::get_cr_file_path test_oacs_dav_put oacs_dav_put (test oacs-dav) test_oacs_dav_put->content::revision::get_cr_file_path cr_fs_path cr_fs_path (private) content::revision::get_cr_file_path->cr_fs_path db_1row db_1row (public) content::revision::get_cr_file_path->db_1row acs_mail_lite::send_immediately acs_mail_lite::send_immediately (private) acs_mail_lite::send_immediately->content::revision::get_cr_file_path content::revision::export_to_filesystem-file content::revision::export_to_filesystem-file (private) content::revision::export_to_filesystem-file->content::revision::get_cr_file_path fs::file_copy fs::file_copy (public) fs::file_copy->content::revision::get_cr_file_path fs::get_object_info fs::get_object_info (public) fs::get_object_info->content::revision::get_cr_file_path fs__datasource fs__datasource (private) fs__datasource->content::revision::get_cr_file_path

Testcases:
apm_tarballs, fs_upload_a_notmpfile, oacs_dav_put

content::revision::get_number (public)

 content::revision::get_number -revision_id revision_id
Switches:
-revision_id
(required)
Returns:
NUMBER

Partial Call Graph (max 5 caller/called nodes):
%3 package_exec_plsql package_exec_plsql (public) content::revision::get_number content::revision::get_number content::revision::get_number->package_exec_plsql

Testcases:
No testcase defined.

content::revision::get_title (public)

 content::revision::get_title -revision_id revision_id

Returns the title of a particular 'content_revision'.

Switches:
-revision_id
(required)
The 'revision_id' of the object
Returns:
The title of the object (text), or empty if not found.
See Also:

Partial Call Graph (max 5 caller/called nodes):
%3 test_attachments_name_api attachments_name_api (test attachments) content::revision::get_title content::revision::get_title test_attachments_name_api->content::revision::get_title test_content_revision content_revision (test acs-content-repository) test_content_revision->content::revision::get_title db_string db_string (public) content::revision::get_title->db_string attachments::get_title attachments::get_title (public) attachments::get_title->content::revision::get_title

Testcases:
content_revision, attachments_name_api

content::revision::import_xml (public)

 content::revision::import_xml -item_id item_id \
    -revision_id revision_id -doc_id doc_id
Switches:
-item_id
(required)
-revision_id
(required)
-doc_id
(required)
Returns:
NUMBER(38)

Partial Call Graph (max 5 caller/called nodes):
%3 package_exec_plsql package_exec_plsql (public) content::revision::import_xml content::revision::import_xml content::revision::import_xml->package_exec_plsql

Testcases:
No testcase defined.

content::revision::index_attributes (public)

 content::revision::index_attributes -revision_id revision_id
Switches:
-revision_id
(required)

Partial Call Graph (max 5 caller/called nodes):
%3 package_exec_plsql package_exec_plsql (public) content::revision::index_attributes content::revision::index_attributes content::revision::index_attributes->package_exec_plsql

Testcases:
No testcase defined.

content::revision::is_latest (public)

 content::revision::is_latest -revision_id revision_id
Switches:
-revision_id
(required)
Returns:
t or f

Partial Call Graph (max 5 caller/called nodes):
%3 test_content_item content_item (test acs-content-repository) content::revision::is_latest content::revision::is_latest test_content_item->content::revision::is_latest test_content_revision content_revision (test acs-content-repository) test_content_revision->content::revision::is_latest package_exec_plsql package_exec_plsql (public) content::revision::is_latest->package_exec_plsql

Testcases:
content_item, content_revision

content::revision::is_live (public)

 content::revision::is_live -revision_id revision_id
Switches:
-revision_id
(required)
Returns:
t or f

Partial Call Graph (max 5 caller/called nodes):
%3 test_content_item content_item (test acs-content-repository) content::revision::is_live content::revision::is_live test_content_item->content::revision::is_live test_content_revision content_revision (test acs-content-repository) test_content_revision->content::revision::is_live package_exec_plsql package_exec_plsql (public) content::revision::is_live->package_exec_plsql packages/acs-developer-support/www/css-edit.tcl packages/acs-developer-support/ www/css-edit.tcl packages/acs-developer-support/www/css-edit.tcl->content::revision::is_live

Testcases:
content_item, content_revision

content::revision::item_id (public)

 content::revision::item_id -revision_id revision_id

Gets the item_id of the item to which the revision belongs.

Switches:
-revision_id
(required)
The revision id
Returns:
The item_id of the item to which this revision belongs

Partial Call Graph (max 5 caller/called nodes):
%3 test_content_revision content_revision (test acs-content-repository) content::revision::item_id content::revision::item_id test_content_revision->content::revision::item_id db_string db_string (public) content::revision::item_id->db_string acs_mail_lite::send_immediately acs_mail_lite::send_immediately (private) acs_mail_lite::send_immediately->content::revision::item_id callback::search::url::impl::file_storage_object callback::search::url::impl::file_storage_object (private) callback::search::url::impl::file_storage_object->content::revision::item_id content::item::get_content content::item::get_content (public) content::item::get_content->content::revision::item_id content::item::get_revision_content content::item::get_revision_content (public) content::item::get_revision_content->content::revision::item_id content::revision::file_stats content::revision::file_stats (private) content::revision::file_stats->content::revision::item_id

Testcases:
content_revision

content::revision::new (public)

 content::revision::new [ -revision_id revision_id ] -item_id item_id \
    [ -title title ] [ -description description ] [ -content content ] \
    [ -mime_type mime_type ] [ -publish_date publish_date ] \
    [ -nls_language nls_language ] [ -creation_date creation_date ] \
    [ -content_type content_type ] [ -creation_user creation_user ] \
    [ -creation_ip creation_ip ] [ -package_id package_id ] \
    [ -attributes attributes ] [ -is_live is_live ] \
    [ -tmp_filename tmp_filename ] [ -storage_type storage_type ]

Adds a new revision of a content item. If content_type is not passed in, we determine it from the content item. This is needed to find the attributes for the content type.

Switches:
-revision_id
(optional)
-item_id
(required)
-title
(optional)
-description
(optional)
-content
(optional)
-mime_type
(optional)
-publish_date
(optional)
-nls_language
(optional)
-creation_date
(optional)
-content_type
(optional)
-creation_user
(optional)
-creation_ip
(optional)
-package_id
(optional)
Package_id content belongs to
-attributes
(optional)
A list of lists of pairs of additional attributes and their values to pass to the constructor. Each pair is a list of two elements: key => value such as [list [list attribute value] [list attribute value]]
-is_live
(defaults to "f") (optional)
True is revision should be set live
-tmp_filename
(optional)
file containing content to be added to revision. The caller is responsible for cleaning up the temporary file.
-storage_type
(optional)
Returns:
Error:
Author:
Dave Bauer <dave@thedesignexperience.org>
Created:
2004-06-04

Partial Call Graph (max 5 caller/called nodes):
%3 test_content_image content_image (test acs-content-repository) content::revision::new content::revision::new test_content_image->content::revision::new test_content_revision content_revision (test acs-content-repository) test_content_revision->content::revision::new acs_object_type::get_table_name acs_object_type::get_table_name (public) content::revision::new->acs_object_type::get_table_name ad_conn ad_conn (public) content::revision::new->ad_conn content::item::get_content_type content::item::get_content_type (public) content::revision::new->content::item::get_content_type content::item::set_live_revision content::item::set_live_revision (public) content::revision::new->content::item::set_live_revision content::revision::update_content content::revision::update_content (private) content::revision::new->content::revision::update_content apm_generate_tarball apm_generate_tarball (public) apm_generate_tarball->content::revision::new content::item::new content::item::new (public) content::item::new->content::revision::new email_image::edit_email_image email_image::edit_email_image (public) email_image::edit_email_image->content::revision::new email_image::new_item email_image::new_item (public) email_image::new_item->content::revision::new fs::add_created_version fs::add_created_version (public, deprecated) fs::add_created_version->content::revision::new

Testcases:
content_image, content_revision

content::revision::read_xml (public)

 content::revision::read_xml -item_id item_id -revision_id revision_id \
    -clob_loc clob_loc
Switches:
-item_id
(required)
-revision_id
(required)
-clob_loc
(required)
Returns:
NUMBER

Partial Call Graph (max 5 caller/called nodes):
%3 package_exec_plsql package_exec_plsql (public) content::revision::read_xml content::revision::read_xml content::revision::read_xml->package_exec_plsql

Testcases:
No testcase defined.

content::revision::replace (public)

 content::revision::replace -revision_id revision_id -search search \
    -replace replace
Switches:
-revision_id
(required)
-search
(required)
-replace
(required)

Partial Call Graph (max 5 caller/called nodes):
%3 package_exec_plsql package_exec_plsql (public) content::revision::replace content::revision::replace content::revision::replace->package_exec_plsql

Testcases:
No testcase defined.

content::revision::revision_name (public)

 content::revision::revision_name -revision_id revision_id
Switches:
-revision_id
(required)
Returns:
VARCHAR2

Partial Call Graph (max 5 caller/called nodes):
%3 test_content_revision content_revision (test acs-content-repository) content::revision::revision_name content::revision::revision_name test_content_revision->content::revision::revision_name package_exec_plsql package_exec_plsql (public) content::revision::revision_name->package_exec_plsql

Testcases:
content_revision

content::revision::to_html (public)

 content::revision::to_html -revision_id revision_id
Switches:
-revision_id
(required)

Partial Call Graph (max 5 caller/called nodes):
%3 package_exec_plsql package_exec_plsql (public) content::revision::to_html content::revision::to_html content::revision::to_html->package_exec_plsql

Testcases:
No testcase defined.

content::revision::to_temporary_clob (public)

 content::revision::to_temporary_clob -revision_id revision_id
Switches:
-revision_id
(required)

Partial Call Graph (max 5 caller/called nodes):
%3 package_exec_plsql package_exec_plsql (public) content::revision::to_temporary_clob content::revision::to_temporary_clob content::revision::to_temporary_clob->package_exec_plsql

Testcases:
No testcase defined.

content::revision::update_attribute_index (public, deprecated)

 content::revision::update_attribute_index
Deprecated. Invoking this procedure generates a warning.

DEPRECATED: the db api for this proc was deleted in 2005

See Also:

Partial Call Graph (max 5 caller/called nodes):
%3 ad_log_deprecated ad_log_deprecated (public) package_exec_plsql package_exec_plsql (public) content::revision::update_attribute_index content::revision::update_attribute_index content::revision::update_attribute_index->ad_log_deprecated content::revision::update_attribute_index->package_exec_plsql

Testcases:
No testcase defined.

content::revision::write_xml (public)

 content::revision::write_xml -revision_id revision_id \
    -clob_loc clob_loc
Switches:
-revision_id
(required)
-clob_loc
(required)
Returns:
NUMBER

Partial Call Graph (max 5 caller/called nodes):
%3 package_exec_plsql package_exec_plsql (public) content::revision::write_xml content::revision::write_xml content::revision::write_xml->package_exec_plsql

Testcases:
No testcase defined.
[ hide source ] | [ make this the default ]

Content File Source

ad_library {

    CRUD procedures for content revisions

    @author Dave Bauer (dave@thedesignexperience.org)
    @creation-date 2004-06-04
    @cvs-id $Id: content-revision-procs.tcl,v 1.36.2.9 2022/09/12 15:30:42 antoniop Exp $
}

namespace eval ::content::revision {}

d_proc -public ::content::revision::new {
    {-revision_id ""}
    {-item_id:required}
    {-title ""}
    {-description ""}
    {-content ""}
    {-mime_type ""}
    {-publish_date ""}
    {-nls_language ""}
    {-creation_date ""}
    {-content_type}
    {-creation_user}
    {-creation_ip}
    {-package_id}
    {-attributes}
    {-is_live "f"}
    {-tmp_filename ""}
    {-storage_type ""}
} {
    Adds a new revision of a content item. If content_type is not
    passed in, we determine it from the content item. This is needed
    to find the attributes for the content type.

    @author Dave Bauer (dave@thedesignexperience.org)
    @creation-date 2004-06-04

    @param revision_id
    @param item_id
    @param content_type
    @param title
    @param description
    @param content
    @param mime_type
    @param publish_date
    @param nls_language
    @param creation_date
    @param creation_user
    @param creation_ip
    @param package_id Package_id content belongs to
    @param is_live True is revision should be set live
    @param tmp_filename file containing content to be added to revision.
           The caller is responsible for cleaning up the temporary file.
    @param package_id
    @param is_live
    @param attributes A list of lists of pairs of additional attributes and
    their values to pass to the constructor. Each pair is a list of two
     elements: key => value such as
    [list [list attribute value] [list attribute value]]

    @return

    @error
} {

    if {![info exists creation_user]} {
        set creation_user [ad_conn user_id]
    }

    if {![info exists creation_ip]} {
        set creation_ip [ad_conn peeraddr]
    }

    if {![info exists content_type] || $content_type eq ""} {
        set content_type [::content::item::get_content_type -item_id $item_id]
    }
    if {$storage_type eq ""} {
        set storage_type [db_string get_storage_type ""]
    }
    if {![info exists package_id]} {
        set package_id [ad_conn package_id]
    }
    set attribute_names ""
    set attribute_values ""

    if { [info exists attributes] && $attributes ne "" } {
        set type_attributes [package_object_attribute_list $content_type]
        set valid_attributes [list]
        # add in extended attributes for this type, ignore
        # content_revision as those are already captured as named
        # parameters to this procedure

        foreach type_attribute $type_attributes {
            if {"cr_revisions" ne [lindex $type_attribute 1]
                && "acs_objects" ne [lindex $type_attribute 1]
            } {
                lappend valid_attributes [lindex $type_attribute 2]
            }
        }
        foreach attribute_pair $attributes {
            lassign $attribute_pair attribute_name attribute_value
            if {$attribute_name in $valid_attributes}  {

            # first add the column name to the list
            append attribute_names  ", ${attribute_name}"
                # create local variable to use for binding
                set $attribute_name $attribute_value
                append attribute_values ", :${attribute_name}"
            }
        }
    }

    set table_name [acs_object_type::get_table_name -object_type $content_type]
    set mime_type [cr_check_mime_type \
                       -filename  $title \
                       -mime_type $mime_type \
                       -file      $tmp_filename]

    set query_text [subst {
        insert into ${table_name}i
        (revision_id, object_type, creation_user, creation_date, creation_ip, title, description, item_id, object_package_id, mime_type $attribute_names)
        values (:revision_id, :content_type, :creation_user, :creation_date, :creation_ip, :title, :description, :item_id, :package_id, :mime_type $attribute_values)
    }]

    db_transaction {
        # An explicit lock was necessary for PostgreSQL between 8.0 and
        # 8.2; left the following statement here for documentary purposes
        #
        # db_dml lock_objects "LOCK TABLE acs_objects IN SHARE ROW EXCLUSIVE MODE"

        if {$revision_id eq ""} {
            set revision_id [db_nextval "acs_object_id_seq"]
        }
        # the postgres "insert into view" is rewritten by the rule into a "select"
        [expr {[db_driverkey ""] eq "postgresql" ? "db_0or1row" : "db_dml"}] \
            insert_revision $query_text

        ::content::revision::update_content \
            -item_id $item_id \
            -revision_id $revision_id \
            -content $content \
            -tmp_filename $tmp_filename \
            -storage_type $storage_type \
            -mime_type $mime_type
    }
    if {[string is true $is_live]} {
        content::item::set_live_revision -revision_id $revision_id
    }
    return $revision_id
}

#
# ::content::revision::collect_cleanup_data
#

d_proc -private ::content::revision::collect_cleanup_data {
    -item_id:required
    -storage_type:required
} {
    return [::content::revision::collect_cleanup_data-$storage_type -item_id $item_id]
}

d_proc -private ::content::revision::collect_cleanup_data-text {
    -item_id:required
} {
    return
}

d_proc -private ::content::revision::collect_cleanup_data-lob {
    -item_id:required
} {
    return
}

d_proc -private ::content::revision::collect_cleanup_data-file {
    -item_id:required
} {
    return [db_list get_files {select content from cr_revisions where item_id = :item_id}]
}

#
# ::content::revision::cleanup
#
d_proc -private ::content::revision::cleanup {
    -storage_type:required
    -storage_area_key:required
    -data:required
} {
    return [::content::revision::cleanup-$storage_type \
                -storage_area_key $storage_area_key \
                -data $data]
}

d_proc -private ::content::revision::cleanup-text {
    -storage_area_key:required
    -data:required
} {
    return
}

d_proc -private ::content::revision::cleanup-lob {
    -storage_area_key:required
    -data:required
} {
    return
}

d_proc -private ::content::revision::cleanup-file {
    -storage_area_key:required
    -data:required
} {

    This function cleans-up files AFTER the DB-entry was deleted.  If
    the transaction is aborted, the file will not be executed and the
    file will survive. Thus function should make
    cr_check_orphaned_files obsolete, which does not scale.

    @see cr_check_orphaned_files
} {
    set dir [cr_fs_path $storage_area_key]
    foreach filename $data {
        ns_log notice "DELETE FILE $dir$filename"
        file delete $dir$filename
    }
}


d_proc -private ::content::revision::check_files {
    {-max_results 5000}
    {-max_checks 10000}
    {-returnlist:boolean}
} {
    Figure out, how many files in the CR are not linked to the
    revisions in the content repository, and report them
    optionally.

    @author Gustaf Neumann

    @param max_results stop after having found so many non-referenced files
    @param max_checks stop after having checked so many non-referenced files
    @param returnlist return the non-referenced files as part of the result
} {
    set paths [cr_fs_path CR_FILES]
    set prefix_length [string length $paths]
    set count 1
    set missing 0
    set files {}
    while {[llength $paths] > 0} {
        # get the first path
        set paths [lassign $paths path]
        #ns_log notice "popping path '$path' form paths, remaining [llength $paths]"

        set children [glob -nocomplain -directory $path *]
        foreach child $children {
            if {[file tail $child] in {. ..}} {
                continue
            }
            if {[file isdirectory $child]} {
                #
                # Using "lappend" leads to a breadth-search: might be
                # slow when the directories a huge, since it takes a
                # while until leaves are found.
                #
                #lappend paths $child

                set paths [lreplace $paths -1 -2 $child]
                #ns_log notice "child is dir $child"
            } else {
                set suffix [string range $child $prefix_length end]
                set success [cr_count_file_entries $suffix]
                if {$success == 0} {
                    ns_log notice "check_files: $count file $child not in db entries"
                    incr missing
                    lappend files $child
                }
                incr count
                if {$count >= $max_checks || $missing >= $max_results} break

            }
        }
        if {$count >= $max_checks || $missing >= $max_results} break
    }
    set msg "$missing of $count files are not ok (not contained in db entries)"
    if {$returnlist_p} {
        append msg \n [join $files \n]
    }
    return $msg
}

d_proc -private ::content::revision::check_dirs {
    {-max_results 5000}
    {-max_checks 10000}
    {-returnlist:boolean}
    {-prune:boolean}
} {
    Figure out, how many directories in the CR are empty, report them
    optionally or delete them optionally.

    @author Gustaf Neumann

    @param max_results stop after having found so many empty directories
    @param max_checks stop after having checked so many directories
    @param prune delete the found empty directories
    @param returnlist return the directories as part of the result
} {
    set paths [cr_fs_path CR_FILES]
    set prefix_length [string length $paths]
    set count 1
    set empty_dirs 0
    set dirs 0
    set empty_dir_list {}
    while {[llength $paths] > 0} {
        # get the first path
        set paths [lassign $paths path]
        #ns_log notice "popping path '$path' form paths, remaining [llength $paths]"

        set children [glob -nocomplain -directory $path *]
        set nr_children 0
        incr dirs
        foreach child $children {
            if {[file tail $child] in {. ..}} {
                continue
            }
            if {[file isdirectory $child]} {
                #
                # Using "lappend" leads to a breadth-search: might be
                # slow when the directories a huge, since it takes a
                # while until leaves are found.
                #
                #lappend paths $child

                set paths [lreplace $paths -1 -2 $child]
                #ns_log notice "child is dir $child"
            }
            incr nr_children
        }
        if {$nr_children == 0} {
            incr empty_dirs
            ns_log notice "check_dirs: directory $path is empty ($empty_dirs out of $dirs)"
            lappend empty_dir_list $path
            if {$prune_p && [regexp {^\d+$} [file tail $path]]} {
                file delete $path
            }
        }
        if {$empty_dirs >= $max_results || $dirs >= $max_checks} {
            break
        }
    }
    set msg "$empty_dirs out of $dirs directories are empty"
    ns_log notice "check_dirs: $msg"
    if {$returnlist_p} {
        append msg \n [join $empty_dir_list \n]
    }
    return $msg
}

d_proc -private ::content::revision::file_stats {
    {-max 10000}
} {

    Determine some basic statistics about files in the CR based on a
    sample. This is useful for large installations with several
    million of files, where a detailed analysis would take very long.

    @author Gustaf Neumann

    @param max number of revisions with storage-type "file" to check
    @result some statistics
} {
    set tuples [db_list_of_lists get_file_names {
        select i.item_id, revision_id, mime_type, content_length
        from cr_items i, cr_revisions r
        where storage_type = 'file'
        and storage_area_key = 'CR_FILES'
        and  r.item_id = i.item_id
        FETCH FIRST :max ROWS ONLY
    }]
    set count 0
    set total_length 0
    set empty_files 0
    foreach tuple $tuples {
        lassign $tuple item_id revision_id mime_type content_length
        incr count
        if {$content_length eq ""} {
            ns_log warning "file_stats: entry has no content_length: revision_id $revision_id mime_type $mime_type"
        } else {
            incr total_length $content_length
        }
        incr mime_types($mime_type)
        incr revisions_for_item($item_id)
        if {$content_length < 1} {
            incr empty_files
        }
    }
    set result ""
    if {$count > 0} {
        set backup_files 0
        set files_with_multiple_revisions 0
        foreach {item_id revs} [array get revisions_for_item] {
            if {$revs > 1} {
                incr files_with_multiple_revisions
                incr backup_files [expr {$revs - 1}]
            }
        }
        set most_common [lrange [lsort \
                                     -integer \
                                     -stride 2 \
                                     -index 1 \
                                     -decreasing \
                                     [array get mime_types]
                                ] 0 11]

        append result \
            "checked files                : $count\n" \
            "files with multiple revisions: $files_with_multiple_revisions\n" \
            "backup files                 : $backup_files\n" \
            "empty files                  : $empty_files\n" \
            "avg file size                : [format %10.2f [expr {$total_length*1.0/$count}]]\n" \
            "mime_types: $most_common"
        ns_log notice "file_stats: $result"
    }
    return $result
}




#
# ::content::revision::update_content
#
d_proc -private ::content::revision::update_content {
    -item_id:required
    -revision_id:required
    -content:required
    -storage_type:required
    -mime_type:required
    {-tmp_filename ""}
} {

    Update content column separately. Oracle does not allow insert
    into a BLOB.

    This assumes that if storage type is lob and no file is specified
    that the content is really text and store it in the text column
    in PostgreSQL

    @author Dave Bauer (dave@thedesignexperience.org)
    @creation-date 2005-02-09

    @param revision_id Content revision to update

    @param content Content to add to resivsion
    @param storage_type text, file, or lob
    @param mime_type mime type of the content
    @param tmp_filename For storage types except 'text'
           a filename can be specified
           instead of 'content'. The caller is responsible
           for cleaning up the temporary file
} {
    ns_log notice "============== update_content-$storage_type $revision_id content '$content' mime_type $mime_type tmp_filename '$tmp_filename'"
    ::content::revision::update_content-$storage_type \
        -item_id $item_id \
        -revision_id $revision_id \
        -content $content \
        -mime_type $mime_type \
        -tmp_filename $tmp_filename
}

d_proc -private ::content::revision::update_content-text {
    -item_id:required
    -revision_id:required
    -content:required
    -mime_type:required
    {-tmp_filename ""}
} {
    db_dml update_content "" -blobs [list $content]

    if {$tmp_filename ne ""} {
        # Traditionally, a provided tmp_file is not handled. I
        # could/should be probably supported in the future.
        ns_log warning "::content::revision::update_content-text: provided tmp_filename is ignored"
    }
}

d_proc -private ::content::revision::update_content-file {
    -item_id:required
    -revision_id:required
    -content:required
    -mime_type:required
    {-tmp_filename ""}
} {
    if {$tmp_filename eq ""} {
        set filename [cr_create_content_file_from_string $item_id $revision_id $content]
    } else {
        set filename [cr_create_content_file $item_id $revision_id $tmp_filename]
    }
    set tmp_size [file size [cr_fs_path]$filename]
    db_dml set_file_content {
        update cr_revisions
        set content = :filename,
            mime_type = :mime_type,
            content_length = :tmp_size
        where revision_id = :revision_id
    }
}

d_proc -private ::content::revision::update_content-lob {
    -item_id:required
    -revision_id:required
    -content:required
    -mime_type:required
    {-tmp_filename ""}
} {
    if {$tmp_filename ne ""} {
        # handle file
        set filename [cr_create_content_file $item_id $revision_id $tmp_filename]
        db_dml set_content "" -blob_files [list $tmp_filename]
        db_dml set_size ""
    } else {
        # handle blob
        db_dml update_content "" -blobs [list $content]
    }
}


d_proc -public content::revision::content_copy {
    -revision_id:required
    {-revision_id_dest ""}
} {
    @param revision_id
    @param revision_id_dest
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
        [list revision_id_dest $revision_id_dest ] \
    ] content_revision content_copy]
}


d_proc -public content::revision::copy {
    -revision_id:required
    {-copy_id ""}
    {-target_item_id ""}
    {-creation_user ""}
    {-creation_ip ""}
} {
    @param revision_id
    @param copy_id
    @param target_item_id
    @param creation_user
    @param creation_ip

    @return NUMBER(38)
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
        [list copy_id $copy_id ] \
        [list target_item_id $target_item_id ] \
        [list creation_user $creation_user ] \
        [list creation_ip $creation_ip ] \
    ] content_revision copy]
}


d_proc -public content::revision::delete {
    -revision_id:required
} {
    @param revision_id
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
    ] content_revision del]
}


d_proc -public content::revision::export_xml {
    -revision_id:required
} {
    @param revision_id

    @return NUMBER(38)
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
    ] content_revision export_xml]
}


d_proc -public content::revision::get_number {
    -revision_id:required
} {
    @param revision_id

    @return NUMBER
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
    ] content_revision get_number]
}


d_proc -public content::revision::import_xml {
    -item_id:required
    -revision_id:required
    -doc_id:required
} {
    @param item_id
    @param revision_id
    @param doc_id

    @return NUMBER(38)
} {
    return [package_exec_plsql -var_list [list \
        [list item_id $item_id ] \
        [list revision_id $revision_id ] \
        [list doc_id $doc_id ] \
    ] content_revision import_xml]
}


d_proc -public content::revision::index_attributes {
    -revision_id:required
} {
    @param revision_id
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
    ] content_revision index_attributes]
}


d_proc -public content::revision::is_latest {
    -revision_id:required
} {
    @param revision_id

    @return t or f
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
    ] content_revision is_latest]
}


d_proc -public content::revision::is_live {
    -revision_id:required
} {
    @param revision_id

    @return t or f
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
    ] content_revision is_live]
}


d_proc -public content::revision::item_id {
    -revision_id:required
} {
  Gets the item_id of the item to which the revision belongs.

  @param  revision_id   The revision id

  @return The item_id of the item to which this revision belongs
} {
    return [db_string item_id {
        select item_id
        from cr_revisions
        where revision_id = :revision_id
    } -default ""]
}


d_proc -public content::revision::read_xml {
    -item_id:required
    -revision_id:required
    -clob_loc:required
} {
    @param item_id
    @param revision_id
    @param clob_loc

    @return NUMBER
} {
    return [package_exec_plsql -var_list [list \
        [list item_id $item_id ] \
        [list revision_id $revision_id ] \
        [list clob_loc $clob_loc ] \
    ] content_revision read_xml]
}


d_proc -public content::revision::replace {
    -revision_id:required
    -search:required
    -replace:required
} {
    @param revision_id
    @param search
    @param replace
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
        [list search $search ] \
        [list replace $replace ] \
    ] content_revision replace]
}


d_proc -public content::revision::revision_name {
    -revision_id:required
} {
    @param revision_id

    @return VARCHAR2
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
    ] content_revision revision_name]
}

d_proc -public content::revision::get_title {
    -revision_id:required
} {

    Returns the title of a particular 'content_revision'.

    @param revision_id The 'revision_id' of the object

    @see content::item::get_title
    @see content::revision::revision_name

    @return The title of the object (text), or empty if not found.

} {
    return [db_string get_title {select title from cr_revisions where revision_id = :revision_id} -default ""]
}

d_proc -public content::revision::to_html {
    -revision_id:required
} {
    @param revision_id
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
    ] content_revision to_html]
}


d_proc -public content::revision::to_temporary_clob {
    -revision_id:required
} {
    @param revision_id
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
    ] content_revision to_temporary_clob]
}


d_proc -public content::revision::write_xml {
    -revision_id:required
    -clob_loc:required
} {
    @param revision_id
    @param clob_loc

    @return NUMBER
} {
    return [package_exec_plsql -var_list [list \
        [list revision_id $revision_id ] \
        [list clob_loc $clob_loc ] \
    ] content_revision write_xml]
}


ad_proc -deprecated content::revision::update_attribute_index {} {
    DEPRECATED: the db api for this proc was deleted in 2005

    @see https://github.com/openacs/openacs-core/commit/1cf48b17dd5faa0a2cbd988ab28d3127d3e25c61#diff-f580056c1afc98a3c8bda629878b7ea8
} {
    return [package_exec_plsql content_revision update_attribute_index]
}


d_proc -public content::revision::get_cr_file_path {
    -revision_id
} {
    Get the path to content in the filesystem
    @param revision_id

    @return path to filesystem stored revision content

    @author Dave Bauer (dave@solutiongrove.com)
    @creation-date 2006-08-27
} {
    # the file path is stored in filename column on oracle
    # and content in PostgreSQL, but we alias to filename so it makes
    # sense
    db_1row get_storage_key_and_path {}
    return [cr_fs_path $storage_area_key]${filename}
}

#
# ::content::revision::export_to_filesystem
#
# This function was previously part of
# fs::publish_versioned_object_to_file_system but the application
# packages should be fully agnostic to the storage_type
# implementation.

d_proc ::content::revision::export_to_filesystem {
    -revision_id:required
    -storage_type:required
    -filename:required
} {
    Export the content of the provided revision to the named file in
    the filesystem.
} {
    ::content::revision::export_to_filesystem-$storage_type \
        -revision_id $revision_id \
        -filename $filename
}

d_proc -private ::content::revision::export_to_filesystem-text {
    -revision_id:required
    -filename:required
} {
    Export the content of the provided revision to the named file in
    the filesystem.
} {
    set content [db_string select_object_content {
        select content from cr_revisions where revision_id = :revision_id
    }]
    set fp [open $filename w]
    puts $fp $content
    close $fp
}

d_proc -private ::content::revision::export_to_filesystem-file {
    -revision_id:required
    -filename:required
} {
    Export the content of the provided revision to the named file in
    the filesystem.
} {
    set cr_file_name [content::revision::get_cr_file_path -revision_id $revision_id]

    #
    # Check if cr_file_name is not empty, otherwise we could end up copying the
    # whole content-repository.
    #
    if {$cr_file_name ne ""} {
        #
        # When there are multiple "unnamed files" in a directory, the
        # constructed filename might exist already. This would lead to an
        # error in the "file copy" operation. Therefore, generate a new
        # name with an alternate suffix in these cases.
        #
        set base_name $filename
        set count 0
        while {[ad_file exists $filename]} {
            set filename $base_name-[incr $count]
        }

        file copy -- $cr_file_name $filename
    } else {
        ad_log Warning "::content::revision::export_to_filesystem-file: \
            cr_file_name is empty (revision_id: $revision_id)"
    }
}

d_proc -private ::content::revision::export_to_filesystem-lob {
    -revision_id:required
    -filename:required
} {
    Export the content of the provided revision to the named file in
    the filesystem.
} {
    db_blob_get_file select_object_content {} -file $filename
}

# Local variables:
#    mode: tcl
#    tcl-indent-level: 4
#    indent-tabs-mode: nil
# End: