# $Id: backup.tcl 1440 2008-05-20 17:51:38Z sergei $
# Export/import of the roster items using an XML file.
# This code provides basic framework for handling roster backup
# files and it's able to serialize/deserialize regular roster contacts.
# Hooks provided to facilitate implementations of storing/restoring
# other kinds of data logically pertaining to the roster
# such as conference bookmarks, annotations, etc.

namespace eval rosterbackup {
    global NS
    set NS(rosterbackup) http://tkabber.jabber.ru/contactlist

    hook::add connected_hook \
	[namespace current]::setup_import_export_menus
    hook::add disconnected_hook \
	[namespace current]::setup_import_export_menus
    hook::add finload_hook \
	[namespace current]::setup_import_export_menus

    hook::add serialize_roster_hook \
	[namespace current]::serialize_roster_contacts
    hook::add deserialize_roster_hook \
	[namespace current]::deserialize_roster_contacts
}

###############################################################################

proc rosterbackup::setup_import_export_menus {args} {
    set emenu [.mainframe getmenu export_roster]
    set imenu [.mainframe getmenu import_roster]

    if {[winfo exists $emenu]} {
	destroy $emenu
    }
    menu $emenu -tearoff 0

    if {[winfo exists $imenu]} {
	destroy $imenu
    }
    menu $imenu -tearoff 0

    if {[jlib::connections] == {}} {
	.mainframe setmenustate export_roster disabled
	.mainframe setmenustate import_roster disabled
    } else {
	.mainframe setmenustate export_roster normal
	.mainframe setmenustate import_roster normal
    }

    foreach c [jlib::connections] {
	set jid [jlib::connection_jid $c]
	set label [format [::msgcat::mc "Roster of %s"] $jid]
	set ecommand [list [namespace current]::export_to_file $c]
	set icommand [list [namespace current]::import_from_file $c]
	$emenu add command -label $label -command $ecommand
	$imenu add command -label $label -command $icommand
    }
}

###############################################################################

proc rosterbackup::export_to_file {connid} {
    set filename [tk_getSaveFile \
		      -initialdir $::configdir \
		      -initialfile [jlib::connection_user $connid]-roster.xml \
		      -filetypes [list \
				      [list [::msgcat::mc "Roster Files"] \
					   .xml] \
				      [list [::msgcat::mc "All Files"] *]]]
    if {$filename == ""} return

    set fd [open $filename w]
    fconfigure $fd -encoding utf-8

    puts $fd {<?xml version="1.0" encoding="UTF-8"?>}
    puts $fd [serialize_roster $connid]

    close $fd
}

###############################################################################

proc rosterbackup::serialize_roster {connid} {
    global NS

    set subtags [list]
    hook::run serialize_roster_hook $connid #[info level] subtags

    jlib::wrapper:createxml [jlib::wrapper:createtag contactlist \
	    -vars [list xmlns $NS(rosterbackup)] -subtags $subtags] \
	-prettyprint 1
}

###############################################################################

proc rosterbackup::serialize_roster_contacts {connid level varName} {
    upvar $level $varName subtags

    set items [list]
    foreach jid [::roster::get_jids $connid] {
	set category [::roster::itemconfig $connid $jid -category]
	switch -- $category {
	    user -
	    gateway {
		lappend items [::roster::item_to_xml $connid $jid]
	    }
	}
    }

    lappend subtags [jlib::wrapper:createtag roster \
	-vars {xmlns jabber:iq:roster} \
	-subtags $items]
}

###############################################################################

proc rosterbackup::import_from_file {connid} {
    set filename [tk_getOpenFile \
		      -initialdir $::configdir \
		      -initialfile [jlib::connection_user $connid]-roster.xml \
		      -filetypes [list \
				      [list [::msgcat::mc "Roster Files"] \
					   .xml] \
				      [list [::msgcat::mc "All Files"] *]]]
    if {$filename == ""} return

    set fd [open $filename r]
    fconfigure $fd -encoding utf-8
    set xml [string trimleft [read $fd] [format %c 0xFEFF]] ;# strip BOM, if any
    close $fd

    deserialize_roster $connid $xml
}

###############################################################################

proc rosterbackup::deserialize_roster {connid data} {
    hook::run roster_deserializing_hook $connid

    set parser [jlib::wrapper:new "#" "#" \
	[list [namespace current]::parse_roster_xml $connid]]
    jlib::wrapper:elementstart $parser stream:stream {} {}
    jlib::wrapper:parser $parser parse $data
    jlib::wrapper:parser $parser configure -final 0
    jlib::wrapper:free $parser

    hook::run roster_deserialized_hook $connid
}

###############################################################################

proc rosterbackup::parse_roster_xml {connid data} {
    global NS

    jlib::wrapper:splitxml $data tag vars isempty cdata children

    if {![string equal $tag contactlist]} {
	return -code error "Bad root element \"$tag\":\
	    must be contactlist"
    }
    set xmlns [jlib::wrapper:getattr $vars xmlns]
    if {![string equal $xmlns $NS(rosterbackup)]} {
	return -code error "Bad root element namespace \"$xmlns\":\
	    must be \"$NS(rosterbackup)\""
    }

    set tuples [list]
    hook::run deserialize_roster_hook $connid $children #[info level] tuples

    if {[llength $tuples] > 0} {
	set scripts [list]
	foreach tuple [lsort -integer -index 0 $tuples] {
	    lappend scripts [lindex $tuple 1]
	}
	[namespace current]::run_deserialization_scripts $scripts
    }
}

###############################################################################

proc rosterbackup::run_deserialization_scripts {scripts} {
    if {[llength $scripts] == 0} return

    uplevel #0 [linsert [lindex $scripts 0] end \
	[list [lindex [info level 0] 0] [lrange $scripts 1 end]]]
}

###############################################################################

proc rosterbackup::deserialize_roster_contacts {connid data level varName} {
    global NS
    upvar $level $varName handlers

    array set existing {}
    foreach jid [::roster::get_jids $connid] {
	set existing($jid) {}
    }

    upvar 0 sent($connid,jids) jids
    set jids [list]
    set subtags [list]

    foreach item $data {
	jlib::wrapper:splitxml $item tag vars isempty cdata children

	if {![string equal $tag roster]} continue
	set xmlns [jlib::wrapper:getattr $vars xmlns]
	if {![string equal $xmlns $NS(roster)]} {
	    return -code error "Bad roster element namespace \"$xmlns\":\
		must be \"$NS(roster)\""
	}

	foreach child $children {
	    set jid [get_item_jid $child]
	    if {![info exists existing($jid)]} {
		lappend jids $jid
		lappend subtags $child
	    }
	}
    }

    if {[llength $subtags] > 0} {
	lappend handlers [list 50 [namespace code [list \
	    send_contacts $connid $subtags]]]
    }

    lappend handlers [list 1000 [namespace code [list \
	show_restore_completion_dialog $connid]]]
}

###############################################################################

proc rosterbackup::get_item_jid {data} {
    jlib::wrapper:splitxml $data ? vars ? ? ?
    jlib::wrapper:getattr $vars jid
}

###############################################################################

proc rosterbackup::send_contacts {connid contacts continuation} {
    global NS

    jlib::send_iq set \
	[jlib::wrapper:createtag query \
	     -vars [list xmlns $NS(roster)] \
	     -subtags $contacts] \
	-connection $connid \
	-command [namespace code [list process_send_result $continuation]]
}

###############################################################################

proc rosterbackup::process_send_result {continuation result xmldata} {
    switch -- $result {
	OK {
	    eval $continuation
	}
	default {
	    # TODO check whether do we need to handle TIMEOUT specially
	    NonmodalMessageDlg [epath] \
		-aspect 50000 \
		-icon error \
		-title [::msgcat::mc "Error"] \
		-message [::msgcat::mc "Error restoring roster contacts: %s" \
		    [error_to_string $xmldata]]
	}
    }
}

###############################################################################

proc rosterbackup::show_restore_completion_dialog {connid continuation} {
    NonmodalMessageDlg [epath] \
	-aspect 50000 \
	-icon info \
	-title [::msgcat::mc "Information"] \
	-message [::msgcat::mc "Roster restoration completed"]
    eval $continuation
}

# vim:ts=8:sw=4:sts=4:noet
