package provide VFS 1.0
package require FTP
proc VirtualTar {tarfile {s {}} {proge {gzip -d}} {upd 0}} {
    global xf zz

    if {$upd == 0} {
	set tarfile $xf(pathi$s)[string trimleft $tarfile "\./"]
    }

    if {[CheckVirtuals "$tarfile" $s $upd]} {
	return
    }
    set zz 0
    set xf(tar.decomp) $proge
    #puts "DEB: tar decompressor: <$proge>"
    set filelist ""
    set block 0
    if {[catch {eval exec $proge < [list $tarfile] | $xf(LISTTAR) -} filelist] \
	    && [CheckTar 1 $filelist block]} {
	if {[catch {eval exec cat < [list $tarfile] | $xf(LISTTAR) -} filelist] \
		&& [CheckTar 2 $filelist block]} {
	    if {[catch {eval exec $xf(LISTTAR) [list $tarfile]} filelist] \
		    && [CheckTar 3 $filelist block]} {
		if {[catch {eval exec tar ztvf [list $tarfile]} filelist] \
			&& [CheckTar 4 $filelist block]} {
		    MessageBox "\[VirtualTar\]: Error opening tar-file\n[string trimleft [file tail $tarfile] \./]!" $s
		    if {$xf(fsmode_$s)} {
			AfterVirtual $s
			FileList $s $xf(pathi$s)
			DefaultInfo $s
		    }
		    return 1
		} {
		    set zz 1
		}
	    }
	}
    } {
	set zz 1
    }

    set filelist [split $filelist \n]
    if {$block} {
	set filelist [lrange $filelist 0 [expr {[llength $filelist]-2}]]
    }
    foreach l $filelist {
	if {![regexp {^[-rwx]+$} [string range $l 1 7]]} {
	    continue
	}
	# MKi kludge for 8.0 handling of list element with quotes in it
	regsub -all -- {\"} $l {\"} l
	regsub / [lindex $l 1] { } ug
	set uid [lindex $ug 0]
	set gid [lindex $ug 1]

	if {[regsub -all -- {-} [lrange $l 3 4] { } tim]} {
	    # GNU tar date
	    set yy [lindex $tim 0]
	    set mm [lindex $tim 1]
	    set dd [lindex $tim 2]
	    set mtim [clock format [clock scan "$mm/$dd/$yy [lindex $tim 3]"] -format "%d %b %y %H:%M"]
	    set y [lindex $l 4]
	} {
	    # old tar date
	    set mtim [clock format [clock scan [lrange $l 3 6]] -format "%d %b %y %H:%M"]
	    set y [lindex $l 6]
	}
	set namei [expr {[string last $y $l] + [string length $y] + 1}]
	regsub {\./} [string range $l $namei end] {} new
	# MKi kludge part II
	set new [subst -nocommands -novariables $new]
	# TODO links
	set list($new) [list [lindex $l 2] [lindex $l 0] $mtim $uid $gid {}]
    }
    unset filelist
    set xf(fsmode_$s) 2

    #VirtualFs $tarfile $s 2 {} $filelist
    #puts "VirtualTar, over"
    VirtualFs $tarfile $s list
}
proc CheckTar {mode files var} {
    upvar $var block
    # 1: $proge (default 'gzip -d') <
    # 2: cat <
    # 3: tar tvf
    # 4: tar ztvf
    if {($mode == 1 || $mode == 4) && [string match "*gzip format*" $files]} {
	return 1
    }
    if {[string match "*blocksize*" [lindex [split $files \n] end]]} {
	set block 1
    }
    return 0
}
proc VirtualZip {zipfile {s {}} {upd 0}} {
    global xf

    if {$upd == 0} {
	set zipfile $xf(pathi$s)[string trimleft $zipfile "\./"]
    }

    if {[CheckVirtuals $zipfile $s $upd]} {
	return
    }

    if {[catch {eval exec $xf(LISTZIP) [list $zipfile]} filelist]} {
	MessageBox "\[VirtualZip\]: Error opening zip-file\n[file tail $zipfile] !" $s
	UnselectAll $s
	return
    } {
	set filelist [split $filelist \n]
	set filelist [lreplace $filelist 0 0]
	set filelist [lreplace $filelist end end]

	foreach l $filelist {
	    # MKi kludge for 8.0 handling of list element with quotes in it
	    regsub -all -- {\"} $l {\"} l
	    regsub -all -- {-} [lrange $l 6 7] { } tim
	    set mtim [clock format [clock scan $tim] -format "%d %b %y %H:%M"]
	    set y [lindex $l 7]
	    set namei [expr {[string last $y $l] + [string length $y] + 1}]
	    regsub {\./} [string range $l $namei end] {} new
	    # MKi kludge part II
	    set new [subst -nocommands -novariables $new]
	    # TODO link
	    set list($new) [list [lindex $l 3] [lindex $l 0] $mtim N/A N/A {}]
	}
	unset filelist
	    
	set xf(fsmode_$s) 1
	#VirtualFs $zipfile $s 3 52 $filelist
	VirtualFs $zipfile $s list
    }
}
proc VirtualLha {file {s {}} {upd 0}} {
    global xf

    if {$upd == 0} {
	set file $xf(pathi$s)[string trimleft $file "\./"]
    }

    if {[CheckVirtuals $file $s $upd]} {
	return
    }

    if {[catch {eval exec $xf(LISTLHA) [list $file]} filelist]} {
	MessageBox "\[VirtualLha\]: Error opening lha-file\n[file tail $file] !" $s
	return
    } {
	set filelist [split $filelist \n]
	#set filelist [lreplace $filelist 0 1]
	#set filelist [lreplace $filelist [expr [llength $filelist]-2] end]
	
	foreach l $filelist {
	    # MKi kludge for 8.0 handling of list element with quotes in it
	    regsub -all -- {\"} $l {\"} l
	    regsub / [lindex $l 1] { } ug
	    set uid [lindex $ug 0]
	    set gid [lindex $ug 1]
	    set y [lindex $l 6]
	    set namei [expr {[string last $y $l] + [string length $y] + 1}]
	    regsub {\./} [string range $l $namei end] {} new
	    # MKi kludge part II
	    set new [subst -nocommands -novariables $new]
	    # TODO link
	    set list($new) [list [lindex $l 2] [lindex $l 0] [lrange $l 4 6] $uid $gid {}]
	}
	unset filelist

	set xf(fsmode_$s) 3
	#VirtualFs $file $s 2 46 $filelist
	VirtualFs $file $s list
    }
}
proc VirtualRpm {file {s {}} {upd 0}} {
    global xf

    if {$upd == 0} {
	set file $xf(pathi$s)[string trimleft $file "\./"]
    }

    if {[CheckVirtuals $file $s $upd]} {
	return
    }

    # TODO
    if {[catch {eval exec $xf(LISTRPMFILES) [list $file]} filelist]} {
	#if [catch {eval exec rpm -qlp [list $file]} filelist] {}
	MessageBox "\[VirtualRpm\]: Error opening rpm-file\n[file tail $file] !" $s
	return
    } {
	if {[catch {eval exec $xf(LISTRPMATTRS) [list $file]} contents]} {
	    #if [catch {eval exec rpm --dump -qlp [list $file]} contents] {}
	    MessageBox "\[VirtualRpm\]: Error reading rpm-file\n[file tail $file] !" $s
	    return
	}
	set filelist [split $filelist \n]
	set contents [split $contents \n]
	#set filelist [lreplace $filelist 0 1]
	#set filelist [lreplace $filelist [expr [llength $filelist]-2] end]
	
	foreach f $filelist l $contents {
	    if {[regsub "$f" $l {} newl] == 0} {
		set newl [lreplace $l 0 [expr {[llength $f]-1}]]
	    }
	    #puts "$l :: $newl"
	    set len [llength $newl]
	    set link {}
	    if {$len == 9} {
		if {[string compare X [lindex $newl end]] == 0} {
		    #puts "dir: $file"
		    append f /
		    set d d
		} {
		    set d l
		    set link " -> [lindex $newl end]"
		}
	    } {
		set d -
	    }
	    set f [string trimleft $f /]
	    set uid [lindex $newl [expr {$len - 6}]]
	    set gid [lindex $newl [expr {$len - 5}]]
	    set mint [lindex $newl [expr {$len - 7}]]
	    set mode [GetBitsFromInt [string range $mint [expr {[string length $mint] - 3}] end] $d]
	    set time [clock format [lindex $newl 1] -format "%d %b %y %H:%M"]
	    
	    set list($f) [list [lindex $newl 0] $mode $time $uid $gid $link]
	    #puts "ADD $f: [list [lindex $newl 0] $mode $time $uid $gid $link]"
	}
	unset filelist contents

	set xf(fsmode_$s) 5
	#VirtualFs $zipfile $s 3 52 $filelist
	VirtualFs $file $s list
    }
}
proc VirtualRar {file {s {}} {upd 0}} {
    global xf

    if {$upd == 0} {
	set file $xf(pathi$s)[string trimleft $file "\./"]
    }

    if {[CheckVirtuals $file $s $upd]} {
	return
    }

    if {[catch {eval exec $xf(LISTRAR) [list $file]} filelist]} {
	MessageBox "\[VirtualRar\]: Error opening rar-file\n[file tail $file] !" $s
	return
    } {
	set filelist [split $filelist \n]
	
	# start from the first ------- line
	set len [llength $filelist]
	for {set i [expr {[lsearch -glob $filelist {-----*}] + 1}]} {$i < $len} {incr i} {
	    #puts "RAR $i :: [lindex $filelist $i]"
	    # skip comments between volumes
	    if {[string match {-----*} [lindex $filelist $i]]} {
		#puts "RAR com $i :: [lindex $filelist $i]"
		incr i
		#puts "RAR com $i :: [lindex $filelist $i]"
		while {[string match {-----*} [lindex $filelist $i]] != 1 && $i < $len} {
		    incr i
		    #puts "RAR com $i :: [lindex $filelist $i]"
		}
		# if end of archive
		if {$i >= $len - 5} {
		    #puts "RAR $i: out"
		    break
		}
		incr i
		#puts "RAR $i :: [lindex $filelist $i]"
	    }
	    # get name
	    set name [string range [lindex $filelist $i] 1 end]
	    # get attrs
	    incr i
	    set l [lindex $filelist $i]
	    regsub {^([^-]+)-([^-]+)-(.+)$} [lindex $l 3] {\2/\1/\3} date
	    #puts "D: [lindex $l 3] :: $date"
	    set time [clock format [clock scan "$date [lindex $l 4]"] -format "%d %b %y %H:%M"]

	    set mode [lindex $l 5]
	    if {[regexp D $mode]} {
		# directory
		append name /
	    }
	    # TODO link
	    set list($name) [list [lindex $l 0] $mode $time N/A N/A {}]
	}
	unset filelist

	#parray list
	set xf(fsmode_$s) 6
	VirtualFs $file $s list
    }
}

proc CheckVirtuals {file s upd} {
    global xf vfs

    if {[string compare {} $s] == 0} {
	after 100 [list InfoChange $file "No files selected!"]
	bell
	after 4000 [list DefaultInfo $file]
	return 1
    }
    if {$upd == 0} {
	if {$xf(fsmode_$s) == 4} {
	    set xf(screen) 0
	    XF_Screen 0 1
	} elseif {$xf(fsmode_left) || $xf(fsmode_right)} {
	    MessageBox "VFS: Sorry, you are already using virtual filesystem!" $s
	    return 1
	}
    } {
	catch {unset vfs}
    }
    if {![file readable $file]} {
	MessageBox "VFS: Unable to open file\n$file\nCheck permissions." $s
	return 1
    }
    return 0
}

# ARG: arr = list is in format:
# index is filepath, item is {size mode mtime uid gid link}
#
# OUTPUT: vfs is in format:
#   dir items:  {name ext "Dir" mode mtime uid gid link}
#   file items: {name ext size mode mtime uid gid link}
proc VirtualFs {filename s arr} {
    global vfs xf
    upvar $arr list

    if {[info exists vfs]} {
	catch {unset vfs}
    }
    set vfs(/) {}
    set vfs(/dirs) {}

    #parray list
    foreach item [lsort [array names list]] {
	#puts "VFS: $item"
	if {[string match */ $item]} {
	    ProcessVFsDir vfs /$item list
	} {
	    #puts "VFS: $item"
	    regexp {[^/]*$} $item file
	    #regsub {^~} [file tail $file] \.\/\~ tail
	    regsub {^~} $file \.\/\~ tail
	    regsub {^\.\/\~} [file rootname $tail] \~ name
	    #puts "VFS: <$file>, <$tail>, <$name>"
	    if {[string match */* $item]} {
		set dir [file dirname $item]/
		if {![info exists vfs(/${dir}dirs)]} {
		    ProcessVFsDir vfs /$dir list
		}
		lappend vfs(/$dir) [list $name [file extension $tail] [lindex $list($item) 0] [lindex $list($item) 1] [lindex $list($item) 2] [lindex $list($item) 3] [lindex $list($item) 4] [lindex $list($item) 5]]
	    } {
		lappend vfs(/) [list $name [file extension $tail] [lindex $list($item) 0] [lindex $list($item) 1] [lindex $list($item) 2] [lindex $list($item) 3] [lindex $list($item) 4] [lindex $list($item) 5]]
	    }
	}
    }
    if {![info exists xf(virtualfile)]} {
	# not an update
	set xf(virtualfile) $filename
	BeforeVirtual $s
    }
    UpdateListbox $s
    DefaultInfo $s
    #puts "VirtualFs, over"
}
proc ProcessVFsDir {arr dir arr2} {
    upvar $arr vfs $arr2 list

    #puts "VFS: dir, $dir"
    set index [string trimleft $dir /]
    if {[string match $dir "/"]} {
	set path ""
    } {
	set dir [string trimright $dir /]
	set path [file dirname $dir]/
	if {[string match "//" $path]} {
	    set path "/"
	}
	set dir [file tail $dir]/
    }

    #puts BAA
    regsub {^~} [file tail $dir] \.\/\~ tail
    regsub {^\.\/\~} [file rootname $tail] \~ name
    #puts "VFS: <$dir>, <$tail>, <$name>"
    if {[info exists vfs(${path}dirs)]} {
	if {[info exists list($index)]} {
	    lappend vfs(${path}dirs) [list $name [file extension $tail]/ "Dir" [lindex $list($index) 1] [lindex $list($index) 2] [lindex $list($index) 3] [lindex $list($index) 4] [lindex $list($index) 5]]
	} {
	    lappend vfs(${path}dirs) [list $name [file extension $tail]/ "Dir" N/A N/A N/A N/A {}]
	}
	if {[info exists vfs(${path}$dir)] == 0} {
	    set vfs(${path}$dir) {}
	}
	if {[info exists vfs(${path}${dir}dirs)] == 0} {
	    set vfs(${path}${dir}dirs) {}
	}
    } {
	ProcessVFsDir vfs $path list
	if {[info exists list($index)]} {
	    lappend vfs(${path}dirs) [list $name [file extension $tail]/ "Dir" [lindex $list($index) 1] [lindex $list($index) 2] [lindex $list($index) 3] [lindex $list($index) 4] [lindex $list($index) 5]]
	} {
	    lappend vfs(${path}dirs) [list $name [file extension $tail]/ "Dir" N/A N/A N/A N/A {}]
	}
	#lappend vfs(${path}dirs) [list $dir $mode]
	if {[info exists vfs(${path}$dir)] == 0} {
	    set vfs(${path}$dir) {}
	}
	set vfs(${path}${dir}dirs) {}
    }
}

proc BeforeVirtual {s} {
    global xf

    set xf(pathfs) $xf(pathi$s)
    SetPaths $s "/"
    set xf(parent_$s) $xf(pathfs)
    $xf(pathEnt_$s) xview moveto 1
    $xf(pathEnt_$s) icursor end
    # initialize vfs historymenu
    set xf(history_$xf(fsmode_$s)) {}

    NullSelect $s

    # mtime sort not for LHA
    #if {$xf(fsmode_$s) == 3} {
	#.xfmenu.butt.m.sort entryconfigure Modifi* -state disabled
    #}

    # tar without delete, disable Move and Delete from popupmenu
    if {$xf(fsmode_$s) == 2 && $xf(notardel)} {
	$xf(fuf).${s}list.popup_menu entryconfigure Mov* -state disabled
	$xf(fuf).${s}list.popup_menu entryconfigure Del* -state disabled
    }
    # disable Show Link Destination and MMB...
    .xfmenu.butt.m entryconfigure Show* -state disabled
    .xfmenu.butt.m entryconfigure MMB* -state disabled

    # disable HyperList
    .xfmenu.file.m entryconfigure Append* -state disabled
    .xfmenu.file.m entryconfigure Show* -state disabled
    #.xfmenu.file.m entryconfigure Clear* -state disabled
    
    set xf(vfs.MMB) $xf(MMB_opens_other)
    set xf(MMB_opens_other) 0
    foreach z {left right} {
	$xf(fuf).$z.copy config -state disabled
    }
    $xf(fuf).${s}list config -bg #eeeece
    grid $xf(fuf).$s.evfs 
}

proc AfterVirtual {s} {
    global xf vfs ftp ftpcac vfsmap

    if {$xf(fsmode_$s) == 4} {
	# FTP
	# if DOWNLOAD LIST is on, exit it first
	if {$xf(smode_$s) == 3} {
	    dirUp $s
	}
	# TODO: check?
	trace vdelete ftp($xf(ftp.sock).status) w XF_BgActive
	set xf(bgact.on) 0
	# close connection
	# TODO, mites tää
	if {[FTP_QUIT $xf(ftp.sock)] == -1} {
	    # some error, force close
	    catch {close $ftp($xf(ftp.sock).datasock)}
	    catch {close $xf(ftp.sock)}
	}

	# clear memory
	FTP_UnsetVariables $xf(ftp.sock)
	# set status info
	set ftp($xf(ftp.sock).status) "User terminated the connection!"

	# save last path
	History $s
	# save FTP site path data
	# make sure dir exists
	file mkdir [file join $xf(user_home) ftp_history]

	if {[catch {open [file join $xf(user_home) ftp_history $xf(ftp.host).new] w} fh] == 0} {
	    # open ok
	    puts $fh $xf(history_4)
	    close $fh
	    file rename -force -- [file join $xf(user_home) ftp_history $xf(ftp.host).new] [file join $xf(user_home) ftp_history $xf(ftp.host)] 
	} {
	    Log $s "Error saving FTP path history!" 1
	}

	# TODO if multiple FTPs open, this is not right
	catch {unset ftpcac}
	#unset xf(ftp.sock)
	unset xf(ftp.host)

	# close possible progress bar
	destroy .__pbar
	# disable screens
	set xf(screen) 0
	.xfmenu.screen.m entryconfigure Normal* -state disabled
	.xfmenu.screen.m entryconfigure FTP* -state disabled
	.xfmenu.butt.m.ftp entryconfigure Clear* -state disabled
	catch {unset xf(screen.side)
	unset xf(screen.path)
	unset xf(screen.sel)
	unset xf(screen.dirs)
	unset xf(screen.files)}

    }

    catch {unset vfs}
    if {[info exists vfsmap]} {
	# buffered files, remove them
	foreach tmp [array names vfsmap $xf(fsmode_$s).*] {
	    #puts "aftervirt DEL: $vfsmap($tmp)"
	    #file delete -force -- $vfsmap($tmp)
	    #puts "aftervirt DEL: [file dirname $vfsmap($tmp)]"
	    file delete -force -- [file dirname $vfsmap($tmp)]
	    unset vfsmap($tmp)
	}
	#unset vfsmap
    }

    SetPaths $s $xf(pathfs)
    # clear vfs historymenu
    catch {unset xf(history_$xf(fsmode_$s))}

    NullSelect $s

    if {$xf(ftp.shortlist)} {
	# enable sort
	.xfmenu.butt.m entryconfigure Sort* -state normal
	.xfmenu.butt.m entryconfigure Column* -state normal
	# restore Column format
	MakeColumnFormat $s
    }
    # enable Show Link Destination and MMB...
    .xfmenu.butt.m entryconfigure Show* -state normal
    .xfmenu.butt.m entryconfigure MMB* -state normal

    # enable HyperList
    .xfmenu.file.m entryconfigure Append* -state normal
    .xfmenu.file.m entryconfigure Show* -state normal
    #.xfmenu.file.m entryconfigure Clear* -state normal

    if {$xf(screen.active) == 0} { 
	set xf(MMB_opens_other) $xf(vfs.MMB)
	unset xf(vfs.MMB)
    }
    # enable mtime sort
    #.xfmenu.butt.m.sort entryconfigure Modifi* -state normal

    # enable Move and Delete from popupmenu
    $xf(fuf).${s}list.popup_menu entryconfigure Mov* -state normal
    $xf(fuf).${s}list.popup_menu entryconfigure Del* -state normal

    foreach z {left right} {
	$xf(fuf).$z.copy config -state normal
    }
    $xf(fuf).${s}list config  -bg [option get $xf(fuf).${s}list background {}]
    set xf(fsmode_$s) 0
    catch {unset xf(virtualfile)}
    #unset xf(pathfs)
    grid remove $xf(fuf).$s.evfs 
}

# Copies the given file as given tmp-file
# params:  s    - side
#          file - file in virtual package (inpath)
#          tmp  - tmp file name (full path)
#
# returns: 1  - error
#          0  - OK
proc VirtualOut {s file tmp {int {}}} {
    global xf ftp TMP
    
    set rc 0
    switch $xf(fsmode_$s) {
	1 {
	    # ZIP
	    if {[catch {eval exec $xf(ZIPOUTP) [list $xf(virtualfile)] [list $file] > [list $tmp]} err]} {
		# error
		set type "ZIP"
		set rc 1
	    }
	}
	2 {
	    # TAR
	    if {[catch {eval exec cat < [list $xf(virtualfile)] | tar Oxf - [list $file] > [list $tmp]} err]} {
		if {[catch {eval exec $xf(tar.decomp) < [list $xf(virtualfile)] | tar Oxf - [list $file] > [list $tmp]} err]} {
		    #puts "DEB: $err"
		    if {[catch {eval exec $xf(TAROUTP) [list $xf(virtualfile)] [list $file] > [list $tmp]} err]} {
			if {[catch {eval exec tar zOxf [list $xf(virtualfile)] [list $file] > [list $tmp]} err]} {
			    set type "TAR"
			    set rc 1
			}
		    }
		}
	    }
	}
	3 {
	    # LHA
	    if {[catch {eval exec $xf(LHAOUTP) [list $xf(virtualfile)] [list $file] > [list $tmp]} err]} {
		set type "LHA"
		set rc 1
	    }
	}
	4 {
	    # FTP
	    # check timeout
	    if {[FO_CheckFTPTimeOut $s $xf(ftp.sock)] || $xf(ftp.aborted)} {
		return 1
	    }
	    # puts back the root that was removed
	    set file /$file
	    FO_SetFTPFileSize $s $file $int
	    #XF_ProgressBar $xf(ftp.sock)
	    XF_PBItem [file tail $file] "Getting"
	    #puts "VOUT ftp: to <$tmp>"
	    if {[FTP_GET $xf(ftp.sock) $file $tmp $xf(ftp.active)] == -1} {
		# error
		set type "FTP"
		set rc 1
		set err $ftp($xf(ftp.sock).status)
	    }
	    #XF_PBFinal
	}
	5 {
	    # RPM
	    cd $TMP(dir)
	    if {[catch {eval exec rpm2cpio [list $xf(virtualfile)] | cpio -iumd --quiet [list $file]} err]} {
		set type "RPM"
		set rc 1
	    } {
		file rename -force -- [file join $TMP(dir) $file] $tmp
		regexp {^([^/]*).*$} $file gfg kuole
		file delete -force -- [file join $TMP(dir) $kuole]
	    }
	}
	6 {
	    # RAR
	    cd $TMP(dir)
	    if {[catch {eval exec $xf(RAROUTP) [list $xf(virtualfile)] [list $file]} err]} {
		set type "RAR"
		set rc 1
	    } {
		file rename -force -- [file tail $file] $tmp
	    }
	}
    }
    if {$rc} {
	MessageBox "$type: Error copying '$file'\n\n$err" $s
    }
    return $rc
}

proc FO_Nor_Virt {s o paths patho filelist intlist operation mode {recurse 0}} {
    global xf zz ftp TMP vdleft vfleft vdright vfright bg

    if {$recurse == 0} {
	if {$xf(fsmode_$o) != 4 && ![file writable $xf(virtualfile)]} {
	    MessageBox "You can not write to\n$xf(virtualfile)\nNo write permission." $s
	    return 1
	}
	set all 0
	set none 0
	#puts "A: $filelist"
	FO_CheckOverWrite $s $o filelist intlist [GetFileNames $o $patho] all none
	#puts "B: $filelist"
	if {[string compare {} $filelist] == 0} {
	    # no files
	    return 0
	}
    }

    switch $mode {
	1 {
	    set voper $xf(TOZIP)
	    set vproc "VirtualZip"
	}
	2 {
	    if {$zz} {
		MessageBox "The tar-archive is compressed! Please uncompress it first." $s
		return 1
	    }
	    set voper $xf(TOTAR)
	    set vproc "VirtualTar"
	}
	3 {
	    set voper $xf(TOLHA)
	    set vproc "VirtualLha"
	}
	4 {
	    # FTP
	    if {[string match {Busy*} $ftp($xf(ftp.sock).status)]} {
		# in FTP mode and busy
		#puts "FTP Busy"
		MessageBox "FTP connection is busy!" $s
		return 1
	    }
	    #puts $filelist
	    if {[FO_CheckFTPTimeOut $s $xf(ftp.sock)]} {
		return 1
	    }
	    set rc 0
	    XF_ProgressBar $xf(ftp.sock)
	    foreach file $filelist int $intlist {
		if {$xf(ftp.aborted)} {return 1}

		#puts "FTP: $file :: $int"
		if {[string match */ $file]} {
		    # dir
		    # TODO
		    if {[FO_FTPPutDir $s $o $paths $patho $file "cp"]} {
			# error
			# TODO askwin continue put
			MessageBox "FTP: Error copying file '$file'\n\n$ftp($xf(ftp.sock).status)" $s
			set rc 1
		    }
		} {
		    # file
		    # allocate space
		    FO_SetFTPLocalFileSize $s $paths $file $int $recurse
		    XF_PBItem $file "Putting"
		    #set idx [expr $int - [set vd${s}(size)]]
		    
		    # v2.00a1 bugifixi MKi
		    FTP_CMD $xf(ftp.sock) "ALLO $ftp($xf(ftp.sock).totalsize)"

		    if {[FTP_PUT $xf(ftp.sock) [file join $paths $file] $patho$file $xf(ftp.active)] == -1} {
			# error
			# TODO: askwin? continue put
			MessageBox "FTP: Error copying file '$file'\n\n$ftp($xf(ftp.sock).status)" $s
			set rc 1
		    }
		}
	    }
	    XF_PBFinal
	    #if {$recurse == 0} {
		#set xf(ftp.update) 1
		#UpdateListbox $o
	    #}
	    # see if FTP is running on bg
	    if {$xf(screen.active)} {
		if {[AskWin "FTP upload is done. Switch back to FTP session?"]} {
		    set xf(screen) 1
		    XF_Screen 1
		}
	    }
	    return $rc
	    # end FTP
	} 
	5 {
	    # RPM
	    MessageBox "Sorry, cannot copy into RPM-packages!" $s	
	    return 1
	}
	6 {
	    # RAR
	    set voper $xf(TORAR)
	    set vproc "VirtualRar"
	}
    }
    set rc 0
    set tmpdir $TMP(dir)vfs[incr TMP(idx)]/
    set temppi [file join $tmpdir [string trimleft $patho /]]/
    # create VFS inpath to TMP
    file mkdir $temppi
    set rc [CopyFiles $s $o $paths $filelist $temppi]
    set rc [FO_WaitSem $rc $vproc]
    if {$rc == 0} {
	cd $tmpdir
	if {[llength $filelist] > 1} {
	    foreach d $filelist {
		set dirri [string trimleft $patho /]
		lappend alist [append dirri "$d"]
	    }
	    set cmd "$voper [list $xf(virtualfile)] $alist"
	} {
	    set dirri [string trimleft $patho /]
	    set alist [append dirri [lindex $filelist 0]]
	    set cmd "$voper [list $xf(virtualfile)] [list $alist]"
	}
	# run command
	set rc [FO_VFS_bg $s $paths $patho $cmd $vproc]

	# update view
	if {$mode == 2} {
	    # TAR
	    $vproc $xf(virtualfile) $o $xf(tar.decomp) 1
	} {
	    # other
	    $vproc $xf(virtualfile) $o 1
	}

	cd $xf(pathfs)
	# delete temp dir
	DelFiles $s $TMP(dir) [file tail $tmpdir]
	
	if {$rc == 0 && [string match $operation "mv"]} {
	    set rc [DelFiles $s $paths $filelist]
	    set rc [FO_WaitSem $rc $vproc]
	}
    }
    return $rc
}

proc FO_Zip {s o paths patho filelist intlist operation op} {
    global xf TMP vfs

    if {[string compare cp $operation] == 0 || [string compare mv $operation] == 0} {
	set all 0
	set none 0
	#puts "A: $filelist"
	#set intlist $xf(selekted$s)    
	FO_CheckOverWrite $s $o filelist intlist [GetFileNames $o $patho] all none
	#puts "B: $filelist"
	if {[string compare {} $filelist] == 0} {
	    # no files
	    return 0
	}
    }
    set inpath [string trimleft $paths /]
    set files {}
    set dirs {}

    foreach f $filelist {
	if {[string match */ $f]} {
	    lappend dirs $inpath${f}*
	} {
	    lappend files $inpath$f
	}
    }

    set tmpdir $TMP(dir)vfs[incr TMP(idx)]/
    file mkdir $tmpdir
    set rmflag 0
    set rc 0
    InfoChange $s "$op..."
    switch $operation {
	rm {
	    
	    set rc [FO_VFS_bg $s $paths $patho "$xf(DELZIP) [list $xf(virtualfile)] $files $dirs" "VirtualZip"]
	    set rmflag 1
	}
	cp {
	    # files can be copied directly
	    if {[string compare {} $files] != 0} {
		set rc [FO_VFS_bg $s $paths $patho "$xf(FROMZIP)j [list $xf(virtualfile)] $files -d [list $patho]" "VirtualZip"]
	    }

	    # dirs must be done through TMP
	    if {$rc == 0 && [string compare {} $dirs] != 0} {
		# copy dirs to TMP
		set rc [FO_VFS_bg $s $paths $patho "$xf(FROMZIP) [list $xf(virtualfile)] $dirs -d $tmpdir" "VirtualZip"]

		if {$rc == 0} {
		    set gg ${tmpdir}$inpath
		    #cd $gg
		    set files [lsort [glob -nocomplain -- $gg{*,.*}]]
		    foreach c [list .. .] {
			set i [lsearch -glob $files *$c]
			set files [lreplace $files $i $i]
		    }
		    #puts "zip: <$files>"
		    # copy selected files to the intended location
		    # TODO maybe move?
		    set rc [CopyFiles $s $o $gg $files $patho]
		    set rc [FO_WaitSem $rc "VirtualZip"]
		}		    
		# remove temp dir
		DelFile $s $TMP(dir) [file tail $tmpdir]
		
		#cd $xf(pathfs)
	    }
	}
	mv {
	    # files can be copied directly
	    if {[string compare {} $files] != 0} {
		set rc [FO_VFS_bg $s $paths $patho "$xf(FROMZIP)j [list $xf(virtualfile)] $files -d [list $patho]" "VirtualZip"]
	    }

	    # dirs must be done through TMP
	    if {$rc == 0 && [string compare {} $dirs] != 0} {
		# copy dirs to TMP
		set rc [FO_VFS_bg $s $paths $patho "$xf(FROMZIP) [list $xf(virtualfile)] $dirs -d $tmpdir" "VirtualZip"]

		if {$rc == 0} {
		    set gg ${tmpdir}$inpath
		    cd $gg
		    set files [lsort [glob -nocomplain -- $gg{*,.*}]]
		    foreach c [list .. .] {
			set i [lsearch -glob $files *$c]
			set files [lreplace $files $i $i]
		    }
		    #puts "zip: <$files>"
		    
		    # copy selected files to the intended location
		    set rc [MoveFiles $s $o $gg $files $patho]
		    set rc [FO_WaitSem $rc "VirtualZip"]
		}

		# get root dir
		#regexp {^([^/]*).*$} $inpath gfg kuole
		# remove temp dir
		DelFile $s $TMP(dir) [file tail $tmpdir]
		
		cd $xf(pathfs)
	    }
	    # if copy went ok, remove files from archive
	    if {$rc == 0} {
		set rc [FO_VFS_bg $s $paths $patho "$xf(DELZIP) [list $xf(virtualfile)] $files $dirs" "VirtualZip"]
	    }
	    set rmflag 1
	}
    }
    if {$rc == 0 && $rmflag} {
	VirtualZip $xf(virtualfile) $s 1
    }
    return $rc
}

proc FO_Tar {s o paths patho filelist intlist operation op} {
    global xf TMP

    if {[string compare cp $operation] == 0 || [string compare mv $operation] == 0} {
	set all 0
	set none 0
	#puts "A: $filelist"
	#set intlist $xf(selekted$s)
	FO_CheckOverWrite $s $o filelist intlist [GetFileNames $o $patho] all none
	#puts "B: $filelist"
	if {[string compare {} $filelist] == 0} {
	    # no files
	    return 0
	}
    }

    set inpath [string trimleft $paths /]
    set tmpdir $TMP(dir)vfs[incr TMP(idx)]/
    file mkdir $tmpdir
    set rmflag 0
    set rc 0
    set files {}

    foreach f $filelist {
	lappend files $inpath$f
    }
    InfoChange $s "$op..."
    switch $operation {
	rm {
	    if {[AskWin "TAR \[Delete\]: This might corrupt\n\
		    \"[string trimleft [file tail $xf(virtualfile)] \./]\"\nProceed ?!"] == 1} {
		set rc [FO_VFS_bg $s $paths $patho "$xf(DELTAR) [list $xf(virtualfile)] $files" "VirtualTar"]
		set rmflag 1
	    } {
		# cancel
		return 1
	    }
	}
	cp {
	    # TODO async
	    if {[catch {eval exec cat < [list $xf(virtualfile)] | tar -C [list $tmpdir] -xf - $files} err]} {
		if {[catch {eval exec $xf(FROMTAR) [list $xf(virtualfile)] -C [list $tmpdir] $files} err]} {
		    if {[catch {eval exec tar -C [list $tmpdir] -zxf [list $xf(virtualfile)] $files} err]} {
			if {[catch {eval exec $xf(tar.decomp) < [list $xf(virtualfile)] | tar -C [list $tmpdir] -xf - $files} err]} {
			    MessageBox "TAR: Cannot copy from\n[string trimleft [file tail $xf(virtualfile)] \./]!\n\n$err" $s
			    return 1
			}
		    }
		}
	    }
	    set gg ${tmpdir}$inpath
	    #cd $gg
	    set files [lsort [glob -nocomplain -- $gg{*,.*}]]
	    foreach c [list .. .] {
		set i [lsearch -glob $files *$c]
		set files [lreplace $files $i $i]
	    }
	    #puts "tar: <$files>"
	    # copy selected files to the intended location
	    set rc [CopyFiles $s $o $gg $files $patho]
	    set rc [FO_WaitSem $rc "VirtualTar"]

	    # remove temp dir
	    DelFile $s $TMP(dir) [file tail $tmpdir]
		
	    #cd $xf(pathfs)
	}
	mv {
	    # TODO async
	    if {[AskWin "TAR \[Move\]: This might corrupt\n\
		    \"[string trimleft [file tail $xf(virtualfile)] \./]\"\nProceed ?!"] == 1} {
		if {[catch {eval exec cat < [list $xf(virtualfile)] | tar -C [list $tmpdir] -xf - $files} err]} {
		    if {[catch {eval exec $xf(FROMTAR) [list $xf(virtualfile)] -C [list $tmpdir] $files} err]} {
			if {[catch {eval exec tar -C [list $tmpdir] -zxf [list $xf(virtualfile)] $files} err]} {
			    if {[catch {eval exec $xf(tar.decomp) < [list $xf(virtualfile)] | tar -C [list $tmpdir] -xf - $files} err]} {
				MessageBox "TAR: Cannot copy from\n[string trimleft [file tail $xf(virtualfile)] \./]!\n\n$err" $s
				return 1
			    }
			}
		    }
		}
		set gg ${tmpdir}$inpath
		#cd $gg
		set files [lsort [glob -nocomplain -- $gg{*,.*}]]
		foreach c [list .. .] {
		    set i [lsearch -glob $files *$c]
		    set files [lreplace $files $i $i]
		}
		#puts "tar: <$files>"
		# copy selected files to the intended location
		set rc [MoveFiles $s $o $gg $files $patho]
		set rc [FO_WaitSem $rc "VirtualTar"]

		# remove temp dir
		DelFile $s $TMP(dir) [file tail $tmpdir]
		
		#cd $xf(pathfs)

		# if copy went ok, remove files from archive
		if {$rc == 0} {
		    set rc [FO_VFS_bg $s $paths $patho "$xf(DELTAR) [list $xf(virtualfile)] $files" "VirtualTar"]

		    set rmflag 1
		}
	    } {
		# cancel
		return 1
	    }
	}
    }
    if {$rc == 0 && $rmflag} {
	VirtualTar $xf(virtualfile) $s $xf(tar.decomp) 1
    }
    return $rc
}

proc FO_Lha {s o paths patho filelist intlist operation op} {
    global xf TMP

    if {[string compare cp $operation] == 0 || [string compare mv $operation] == 0} {
	set all 0
	set none 0
	#puts "A: $filelist"
	#set intlist $xf(selekted$s)
	FO_CheckOverWrite $s $o filelist intlist [GetFileNames $o $patho] all none
	#puts "B: $filelist"
	if {[string compare {} $filelist] == 0} {
	    # no files
	    return 0
	}
    }

    set inpath [string trimleft $paths /]
    set files {}
    set dirs {}

    foreach f $filelist {
	if {[string match */ $f]} {
	    lappend dirs $inpath${f}*
	} {
	    lappend files $inpath$f
	}
    }

    set tmpdir $TMP(dir)vfs[incr TMP(idx)]/
    file mkdir $tmpdir
    set rmflag 0
    set rc 0
    InfoChange $s "$op..."
    switch $operation {
	rm {
	    set rc [FO_VFS_bg $s $paths $patho "$xf(DELLHA) [list $xf(virtualfile)] $files $dirs" "VirtualLha"]

	    set rmflag 1
	}
	cp {
	    # files can be copied directly
	    if {[string compare {} $files] != 0} {
		set rc [FO_VFS_bg $s $paths $patho "$xf(FROMLHA)iw=[list $patho] [list $xf(virtualfile)] $files" "VirtualLha"]
	    }

	    # dirs must be done through TMP
	    if {$rc == 0 && [string compare {} $dirs] != 0} {
		# copy dirs to TMP
		set rc [FO_VFS_bg $s $paths $patho "$xf(FROMLHA)w=[list $tmpdir] [list $xf(virtualfile)] $dirs" "VirtualLha"]

		if {$rc == 0} {
		    set gg ${tmpdir}$inpath
		    #cd $gg
		    set files [lsort [glob -nocomplain -- $gg{*,.*}]]
		    foreach c [list .. .] {
		    set i [lsearch -glob $files *$c]
			set files [lreplace $files $i $i]
		    }
		    #puts "lha: <$files>"
		    # copy selected files to the intended location
		    set rc [CopyFiles $s $o $gg $files $patho]
		    set rc [FO_WaitSem $rc "VirtualLha"]
		}

		# remove temp dir
		DelFile $s $TMP(dir) [file tail $tmpdir]
		
		#cd $xf(pathfs)
	    }
	}
	mv {
	    # files can be copied directly
	    if {[string compare {} $files] != 0} {
		set rc [FO_VFS_bg $s $paths $patho "$xf(FROMLHA)iw=[list $patho] [list $xf(virtualfile)] $files" "VirtualLha"]
	    }

	    # dirs must be done through TMP
	    if {$rc == 0 && [string compare {} $dirs] != 0} {
		# copy dirs to TMP
		set rc [FO_VFS_bg $s $paths $patho "$xf(FROMLHA)w=[list $tmpdir] [list $xf(virtualfile)] $dirs" "VirtualLha"]

		if {$rc == 0} {
		    set gg ${tmpdir}$inpath
		    #cd $gg
		    set files [lsort [glob -nocomplain -- $gg{*,.*}]]
		    foreach c [list .. .] {
			set i [lsearch -glob $files *$c]
			set files [lreplace $files $i $i]
		    }
		    #puts "lha: <$files>"
		    # copy selected files to the intended location
		    set rc [MoveFiles $s $o $gg $files $patho]
		    set rc [FO_WaitSem $rc "VirtualLha"]
		}

		# remove temp dir
		DelFile $s $TMP(dir) [file tail $tmpdir]
		
		#cd $xf(pathfs)
	    }
	    # if copy went ok, remove files from archive
	    if {$rc == 0} {
		set rc [FO_VFS_bg $s $paths $patho "$xf(DELLHA) [list $xf(virtualfile)] $files $dirs" "VirtualLha"]

		set rmflag 1
	    }
	}
    }
    if {$rc == 0 && $rmflag} {
	VirtualLha $xf(virtualfile) $s 1
    }
    return $rc
}

proc FO_Rar {s o paths patho filelist intlist operation op} {
    global xf TMP

    if {[string compare cp $operation] == 0 || [string compare mv $operation] == 0} {
	set all 0
	set none 0
	#puts "A: $filelist"
	#set intlist $xf(selekted$s)
	FO_CheckOverWrite $s $o filelist intlist [GetFileNames $o $patho] all none
	#puts "B: $filelist"
	if {[string compare {} $filelist] == 0} {
	    # no files
	    return 0
	}
    }

    set tmpdir $TMP(dir)vfs[incr TMP(idx)]/
    file mkdir $tmpdir
    set inpath [string trimleft $paths /]
    set files {}
    set dirs {}

    foreach f $filelist {
	if {[string match */ $f]} {
	    lappend dirs $inpath${f}*
	} {
	    lappend files $inpath$f
	}
    }

    InfoChange $s "$op..."
    set rc 0
    set rmflag 0
    switch $operation {
	rm {
	    set rc [FO_VFS_bg $s $paths $patho "$xf(DELRAR) [list $xf(virtualfile)] $files $dirs" "VirtualRar"]

	    set rmflag 1
	}
	cp {
	    # files without paths
	    set rc [FO_VFS_bg $s $paths $patho "$xf(RAROUTP) [list $xf(virtualfile)] $files $patho" "VirtualRar"]

	    # dirs must be done through TMP
	    if {$rc == 0 && [string compare {} $dirs] != 0} {
		# copy dirs to TMP
		set rc [FO_VFS_bg $s $paths $patho "$xf(FROMRAR) [list $xf(virtualfile)] $dirs [list $tmpdir]" "VirtualRar"]
		
		if {$rc == 0} {
		    set gg ${tmpdir}$inpath
		    #cd $gg
		    set files [lsort [glob -nocomplain -- $gg{*,.*}]]
		    foreach c [list .. .] {
			set i [lsearch -glob $files *$c]
			set files [lreplace $files $i $i]
		    }
		    #puts "rar: <$files>"
		    # copy selected files to the intended location
		    set rc [CopyFiles $s $o $gg $files $patho]
		    set rc [FO_WaitSem $rc "VirtualRar"]
		}

		# remove temp dir
		DelFile $s $TMP(dir) [file tail $tmpdir]
		
		#cd $xf(pathfs)
	    }
	}
	mv {
	    # files without paths
	    set rc [FO_VFS_bg $s $paths $patho "$xf(RAROUTP) [list $xf(virtualfile)] $files $patho" "VirtualRar"]
		
	    # dirs must be done through TMP
	    if {$rc == 0 && [string compare {} $dirs] != 0} {
		# copy dirs to TMP
		set rc [FO_VFS_bg $s $paths $patho "$xf(FROMRAR) [list $xf(virtualfile)] $dirs [list $tmpdir]" "VirtualRar"]
		
		if {$rc == 0} {
		    set gg ${tmpdir}$inpath
		    #cd $gg
		    set files [lsort [glob -nocomplain -- $gg{*,.*}]]
		    foreach c [list .. .] {
			set i [lsearch -glob $files *$c]
			set files [lreplace $files $i $i]
		    }
		    #puts "rar: <$files>"
		    # copy selected files to the intended location
		    set rc [MoveFiles $s $o $gg $files $patho]
		    set rc [FO_WaitSem $rc "VirtualRar"]
		}

		# remove temp dir
		DelFile $s $TMP(dir) [file tail $tmpdir]
		
		#cd $xf(pathfs)
	    }
	    # if ok, remove files from archive
	    if {$rc == 0} {
		set rc [FO_VFS_bg $s $paths $patho "$xf(DELRAR) [list $xf(virtualfile)] $files $dirs" "VirtualRar"]

		set rmflag 1
	    }
	}
    }
    if {$rc == 0 && $rmflag} {
	VirtualRar $xf(virtualfile) $s 1
    }
    return $rc
}

proc VirtualFTP {s address} {
    global xf ftp prompt falists falist

    if {$xf(fsmode_left) || $xf(fsmode_right) || $xf(screen.active)} {
	MessageBox "Sorry, you are already using virtual filesystem!" $s
	set xf(pathi$s) $xf(backup_$s)
	UpdateListbox $s
	return 1
    }

    #puts $address
    regexp {^ftp://(.*)$} [string tolower $address] gfg address
    #puts $address
    set host {}
    set user {}
    set path {}
    set passwd {}
    # look in the addressbook first
    if {[lsearch -exact $falists $address] > -1} {
	# alias defined, use it
	Log $s "FTP address alias found!" 3
	if {[regexp {[:/]} [lindex $falist($address) 0]]} {
	    # address includes initial path
	    regexp {^([^:/]*)[:]?(.*)$} [lindex $falist($address) 0] gfg host path
	} {
	    # host only
	    set host [lindex $falist($address) 0]
	    set path {}
	}
	set user [lindex $falist($address) 2]
	if {[lindex $falist($address) 1] == 1} {
	    # anonymous
	    set passwd [lindex $falist($address) 3]
	} {
	    # normal
	    set passwd [Decrypt [lindex $falist($address) 3]]
	}
	set xf(ftp.port) [lindex $falist($address) 4]
	set xf(ftp.chunk) [lindex $falist($address) 5]
	set xf(ftp.timeout) [lindex $falist($address) 6]
	set xf(ftp.active) [expr {![lindex $falist($address) 7]}]
	set xf(ftp.shortlist) [lindex $falist($address) 8]
    } {
	# no alias, parse address
	if {[regexp @ $address]} {
	    # user included
	    regexp {^([^@]*)@(.*)$} [string tolower $address] gfg user rest
	} {
	    set rest [string tolower $address]
	}
	
	if {[regexp {[:/]} $rest]} {
	    # address includes initial path
	    regexp {^([^:/]*)[:]?(.*)$} $rest gfg host path
	} {
	    # host only
	    set host $rest
	    set path {}
	}
	# default values TODO resources??
	set xf(ftp.port) 21
	set xf(ftp.chunk) 2048
	# TODO 30/60??
	set xf(ftp.timeout) 60
	set xf(ftp.active) 1
    }
    #set xf(ftp.shortlist) $xf(ftp_shortlist)

    # prompt only if one of host, user or passwd is missing
    if {[string compare {} $host] == 0 \
	    || [string compare {} $user] == 0 \
	    || [string compare {} $passwd] == 0} {
	set prompt(1.label) "Host: "
	set prompt(1.result) $host
	
	set prompt(2.label) "Init. path: "
	set prompt(2.result) $path
	
	set prompt(3.label) "Username: "
	set prompt(3.result) $user
	
	set prompt(4.label) "Password: "
	set prompt(4.result) $passwd
	set prompt(4.passwd) {}
	
	set focus 3
	if {[string compare {} $user] != 0} {
	    incr focus
	}

	if {[DialogWin "FTP parameters" -50 -50 0 {} {} {} $focus] == 0} {
	    # login aborted
	    unset prompt
	    set xf(pathi$s) $xf(backup_$s)
	    UpdateListbox $s
	    return
	}
	# get the host
	set host $prompt(1.result)
	# get initial path
	set path $prompt(2.result)
	# get the user
	set user $prompt(3.result)
	# get passwd
	set passwd $prompt(4.result)
	unset prompt
    }

    #puts "<$user>@<$host>:<$path>"
    # set up FTP logging
    # this is needed so that FTP library gets loaded
    FTP_LoadPackage

    set xf(bgact.side) $s
    if {[lsearch [info procs FTP_L*] FTP_Log] > -1 \
	    && [llength [info procs FO_FTPLog]] == 1} {
	rename FTP_Log ""
	rename FO_FTPLog FTP_Log
    }

    Log $s "Opening FTP connection: <$user>@<$host>:<$path>" 3
    set commSock [FTP_Open $host $user $passwd $xf(ftp.port) $xf(ftp.chunk) $xf(ftp.timeout)]
    if {$commSock == -1} {
	MessageBox "Error opening FTP connection to:\n$host\n\n$ftp($commSock.status)" $s
	set xf(pathi$s) $xf(backup_$s)
	UpdateListbox $s
	return
    } {
	set xf(fsmode_$s) 4
	set xf(ftp.host) $host
	set xf(ftp.user) $user
	set xf(ftp.pass) $passwd
	set xf(ftp.sock) $commSock
	DefaultInfo $s

	# socket open
	set ftp($commSock.statenable) 1
	set ftp($commSock.statallowed) 1
	#set ftp($commSock.chunk) $xf(ftp.chunk)
	trace variable ftp($commSock.status) w XF_BgActive
	set xf(bgact.side) $s

	# if initial path was given, change there
	if {[string compare {} $path] != 0} {
	    if {[FTP_CMD $commSock "CWD $path"] == -1} {
		MessageBox "FTP: Unable to change initial path to\n$path\nStarting from default." $s
	    }
	}
	# get the current path
	FTP_CMD $commSock PWD
	regexp {.*\"(.*)\".*} $ftp($commSock.reply) gfg path
	set path [string trimright $path /]/
	#puts $path

	# ok so far
	set xf(pathfs) $xf(backup_$s)
	SetPaths $s $path
	if {[string compare / $path] == 0} {
	    set xf(parent_$s) $xf(pathfs)
	}
	# initialize vfs historymenu
	# TODO should we change to the last path?
	set xf(history_4) {}
	if {[file exists [file join $xf(user_home) ftp_history $host]]} {
	    # old data exists
	    if {[catch {open [file join $xf(user_home) ftp_history $host] r} fh] == 0} {
		# open ok
		set xf(history_4) [read $fh]
		close $fh
	    }
	}

	#$xf(pathEnt_$s) xview moveto 1
	#$xf(pathEnt_$s) icursor end

	NullSelect $s

	.xfmenu.butt.m entryconfigure MMB* -state disabled
	set xf(vfs.MMB) $xf(MMB_opens_other)
	set xf(MMB_opens_other) 0
	.xfmenu.butt.m.ftp entryconfigure Clear* -state normal
	if {$xf(ftp.shortlist)} {
	    # short list TODO
	    .xfmenu.butt.m entryconfigure Show* -state disabled
	    .xfmenu.butt.m entryconfigure Sort* -state disabled
	    .xfmenu.butt.m entryconfigure Column* -state disabled
	    # set column format to show only name
	    set xf(cf_$s) "\[format \"%-[lindex $xf(cf.name) 1]s\n\" \$name\]"
	    # disable mtime sorting in FTP long list
	    #.xfmenu.butt.m.sort entryconfigure Modifi* -state disabled
	}

	foreach z {left right} {
	    $xf(fuf).$z.copy config -state disabled
	}
	# show exit button
	grid $xf(fuf).$s.evfs
	# enable screens
	set xf(screen) 1
	.xfmenu.screen.m entryconfigure Normal* -state normal
	.xfmenu.screen.m entryconfigure FTP* -state normal
	$xf(fuf).${s}list config -bg #eeeece -state normal
	$xf(fuf).${s}list delete 0.0 end
	set xf(ftp.update) 1
	FileList $s $path
	#DefaultInfo $s
    }
}
# Check if FTP connection has timed out and do what is needed
proc FO_CheckFTPTimeOut {s socket} {
    global ftp

    if {$ftp($socket.timeout) == 1} {
	# timeout has happened
	# TODO
	#puts "FTP: Timeout"
	Log $s "FTP: connection timed out!" 3
	if {[AskWin "FTP connection has timed out, reconnect?"] == 0} {
	    # no, close
	    AfterVirtual $s
	    return 1
	}
	FO_FTPReconnect $s
    }
    # all OK
    return 0
}

# Procedure for making reconnection
proc FO_FTPReconnect {s} {
    global xf ftp

    Log $s "FTP: reconnecting to $xf(ftp.host)" 3
    set commSock [FTP_Open $xf(ftp.host) $xf(ftp.user) $xf(ftp.pass) $xf(ftp.port) $xf(ftp.chunk) $xf(ftp.timeout)]
    if {$commSock == -1} {
	MessageBox "Error reconnecting FTP connection to:\n$host\n\n$ftp($commSock.status)" $s
	AfterVirtual $s
	return 1
    }
    #puts "old socket: $xf(ftp.sock)"
    #puts "new socket: $commSock"

    if {[string compare $commSock $xf(ftp.sock)] != 0} {
	# not same identifier, clean old data
	# we still cannot do this... FTPlib makes noise...
	#FTP_UnsetVariables $xf(ftp.sock)
    }
    set xf(ftp.sock) $commSock
    set ftp($commSock.statenable) 1
    set ftp($commSock.statallowed) 1
    # connect ok, cwd
    FTP_CMD $commSock "CWD $xf(pathi$s)"
    return 0
}

# Override ftplib log procedure
proc FO_FTPLog {str} {
    global xf
    if {[string match *<<* $str] || [string match *>>* $str]} {
	Log $xf(bgact.side) [string trimleft $str <>] 3
    }
}

proc FO_FTP {s o paths patho filelist intlist operation op {recursive 0}} {
    global xf ftp bg TMP

    set rc 0
    set n [llength $filelist]
    set index 0
    
    if {[FO_CheckFTPTimeOut $s $xf(ftp.sock)]} {
	return 1
    }

    if {$recursive == 0 && \
	    ([string compare cp $operation] == 0 \
	    || [string compare mv $operation] == 0)} {
	set all 0
	set none 0
	#puts "A: $filelist"
	FO_CheckOverWrite $s $o filelist intlist [GetFileNames $o $patho] all none
	#puts "B: $filelist"
    }

    XF_ProgressBar $xf(ftp.sock)
    DEBUG "FO_FTP(): $paths"
    foreach file $filelist int $intlist {
	DEBUG "FO_FTP(): <$file> :: >$int<"
	if {$xf(ftp.aborted)} {return 1}

	# check if DOWNLOAD LIST or normal
	if {$xf(smode_$s) == 3} {
	    # DOWNLOAD LIST
	    set paths [file dirname $file]/
	    set target [file tail $file]
	} {
	    # normal
	    set target $file
	}

	InfoChange $s "$op: [incr index] / $n"
	if {[string match */ $file]} {
	    # dir
	    set file [string trimright $file /]
	    set target [string trimright $target /]
	    switch $operation {
		rm {
		    if {[DelSingleClipFile $s $file]} {
			continue
		    }
		    if {[FTP_CMD $xf(ftp.sock) "RMD $file"] == -1} {
			# not empty directory, try recursive
			if {[FO_FTPdir $s $o $paths $patho $target "rm"]} {
			    MessageBox "FTP: Error deleting contents of '$file/'\n\n$ftp($xf(ftp.sock).status)" $s
			    set rc 1
			    # TODO: abort/continue
			    continue
			} {
			    # now directory is empty
			    if {[FTP_CMD $xf(ftp.sock) "RMD $file"] == -1} {
				# still error
				MessageBox "FTP: Error deleting '$file/'\n\n$ftp($xf(ftp.sock).status)" $s
				set rc 1
				# TODO: abort/continue
				continue
			    }
			}
		    }
		    set xf(ftp.update) 1
		}
		cp {
		    # TODO test
		    if {[FTP_GET $xf(ftp.sock) $file.tar.gz $patho$target.tar.gz $xf(ftp.active)] == -1} {
			# server does not support packaged dirs, try recursive
			#puts "FTP: cannot get $file as package, recursive"
			set rc [FO_FTPdir $s $o $paths $patho $target "cp"]
		    } {
			# unpack the package
			#puts "FTP: got packaged dir $file"
			if {[catch {eval exec gunzip < [file join $patho $target.tar.gz] | tar -C $patho -xf -} err]} {
			    MessageBox "FTP: Get directory '$file/'. Error unpacking $target.tar.gz\n\n$err" $s
			    set rc 1
			    # TODO: abort/continue
			    continue
			} {
			    # delete the package
			    file delete -force -- [file join $patho $target.tar.gz]
			}
		    }		    
		}
		mv {
		    set xf(noinfo) 1
		    set rc [FO_FTP $s $o $paths $patho $file $int "cp" $op]
		    if {$rc == 0} {
			# no error, delete
			set rc [FO_FTP $s $o $paths $patho $file $int "rm" $op]
		    }
		    set xf(noinfo) 0
		}
	    }
	} {
	    # file
	    switch $operation {
		rm {
		    if {[DelSingleClipFile $s $file]} {
			continue
		    }
		    if {[FTP_CMD $xf(ftp.sock) "DELE $file"] == -1} {
			# error
			MessageBox "FTP: Error deleting '$file'\n\n$ftp($xf(ftp.sock).status)" $s
			# TODO: abort or continue
			set rc 1
			continue
		    }
		    set xf(ftp.update) 1
		}
		cp {
		    DEBUG "$paths, $target"
		    FO_SetFTPFileSize $s $paths$target $int
		    XF_PBItem $file "Getting"
		    if {[FTP_GET $xf(ftp.sock) $file $patho$target $xf(ftp.active)] == -1} {
			# error
			MessageBox "FTP: Error copying '$file'\n\n$ftp($xf(ftp.sock).status)" $s
			# TODO: is this needed
			#set rc [CopyCatch $s $file $patho]
			set rc 1
		    }
		}
		mv {
		    set xf(noinfo) 1
		    set rc [FO_FTP $s $o $paths $patho $file $int "cp" $op]
		    if {$rc == 0} {
			# no error, delete
			set rc [FO_FTP $s $o $paths $patho $file $int "rm" $op]
		    }
		    set xf(noinfo) 0
		}
	    }
	}
    }
    XF_PBFinal

    # execute notify command
    if {$xf(ftp.doendcommand)} {
	set fileid [eval "bg_exec [list $xf(ftp.endcommand)] {} 1 1 $TMP(dir)"]
	set bg($fileid.stoptime) "RUNNING!"
	#vwait bg($fileid.closed)
	#if {$bg($fileid.error)} {
	#    tkerror "FTP BACKGROUND ERROR:\n$bg($fileid.errormsg)" $s
	#}
    }
    # see if FTP is running on bg
    if {$xf(screen.active)} {
	# show message
	if {$xf(ftp.doendmessage)} {
	    if {[AskWin "FTP download is done. Switch back to FTP session?"]} {
		set xf(screen) 1
		XF_Screen
	    }
	}
    }
    return $rc
}
proc FO_SetFTPFileSize {s file index} {
    global xf ftp vfleft vfright vdleft vdright

    #puts "ftp active: $xf(screen.active)"
    if {$xf(screen.active) || \
	    $xf(ftp.shortlist) || \
	    $xf(smode_$s) == 3 || \
	    [string compare {} $index] == 0} {
	puts "ftp getting file size: $file"
	# if short list, get file size
	# TODO change this
	if {[FTP_LIST $xf(ftp.sock) 0 $xf(ftp.active) $file] == -1} {
	    # error
	    set size 0
	} {
	    puts ">>$ftp($xf(ftp.sock).readed)<<"
	    set size [lindex $ftp($xf(ftp.sock).readed) 4]
	    #[lindex $ftp($xf(ftp.sock).reply) 1]
	}
	DEBUG "bugi $size"
    } {
	set ind [expr {$index - [llength [set vd$s]]}]
	set size [lindex [lindex [set vf$s] $ind] 2]
    }
    # MKi 981028 temp fix for getting file size for file that has 
    # space somewhere in the path or name
    # Now progress bar will not crash
    if {[string compare {} $size] == 0} {
	set size 0
    }
    set ftp($xf(ftp.sock).currsize) 0
    set ftp($xf(ftp.sock).starttime) [clock seconds]
    set ftp($xf(ftp.sock).totalsize) $size
}
# TODO
proc FO_SetFTPLocalFileSize {s path file index recurse} {
    global xf ftp vfleft vfright vdleft vdright

    if {$xf(screen.active) || $recurse || [string compare {} $index] == 0} {
	# if recursive or screen is active, get file size
	if {[catch {file size $path$file} size]} {
	    # error
	    set size 0
	}
    } {
	set ind [expr {$index - [llength [set vd$s]]}]
	set size [lindex [lindex [set vf$s] $ind] 2]
    }
    set ftp($xf(ftp.sock).currsize) 0
    set ftp($xf(ftp.sock).starttime) [clock seconds]
    set ftp($xf(ftp.sock).totalsize) $size
}

proc FO_FTPdir {s o paths patho dir operation} {
    global xf ftp

    set rc 0
    # create destination dir, if copy or move
    if {[string compare $operation rm] != 0 && \
	    [catch {file mkdir [list $patho$dir]} err]} {
	MessageBox "FTP: Unable to create target directory '$patho$dir/'\n\n$err" $s
	return 1
    }
	
    # change dir
    if {[FTP_CMD $xf(ftp.sock) "CWD $paths$dir"] == -1} {
	# error
	return 1
    }
    if {[FTP_LIST $xf(ftp.sock) 1 $xf(ftp.active)] == -1} {
	# error, list is empty
	MessageBox "FTP: Unable to get file listing...\n\n$ftp($xf(ftp.sock).status)" $s
	return 1
    }
    set list [split $ftp($xf(ftp.sock).readed) \n]
    set list [lreplace $list 0 0]
    #puts "FTPdir: <$list>"
    set filelist {}
    foreach f $list {
	lappend filelist [string trimright $f \*\@]
	#puts $filelist
    }
    #puts "FTPdir: <$filelist>"

    set xf(noinfo) 1
    set rc [FO_FTP $s $o $paths$dir/ $patho$dir/ $filelist {} $operation {} 1]
    set xf(noinfo) 0
    # change back to parent dir
    FTP_CMD $xf(ftp.sock) "CWD $paths"
    return $rc
}

proc FO_FTPPutDir {s o paths patho dir operation} {
    global xf ftp

    set rc 0
    # create dir to FTP
    if {[FTP_CMD $xf(ftp.sock) "MKD $patho[string trimright $dir /]"] == -1} {
	# error
	return 1
    }
    # get local files
    #cd $xf(pathi$s)$dir
    set fil [glob -nocomplain -- $paths$dir{.*,*}]
    set sel 0
    set files {}
    foreach f $fil {
	if {[file isdirectory $f]} {
	    if {![string match {*\.} $f] && ![string match {*\.\.} $f]} {
		lappend files [file tail $f]/
		lappend selected [incr sel]
	    }
	} {
	    lappend files [file tail $f]
	    lappend selected [incr sel]
	}	    
    }
    #puts "copy $dir <$files>"
    
    if {[string compare {} $files] != 0} {
	#puts "copy $dir 1"
	if {[FTP_CMD $xf(ftp.sock) "CWD $patho$dir"] == -1} {
	    # error
	    return 1
	}
	#puts "copy $dir 2"
	set xf(noinfo) 1
	# copy files
	set rc [FO_Nor_Virt $s $o $paths$dir $patho$dir $files $selected "cp" 4 1]
	set xf(noinfo) 0
	
	#puts "copy $dir 3"
	# change back to parent dir
	if {[FTP_CMD $xf(ftp.sock) "CWD $patho"] == -1} {
	    # error
	    return 1
	}
	#puts "copy $dir 4"
    }
    return $rc
}

# args:  force  --  in FTP, force directory update
#        nolist --  in normal fs, skip list update (for starting VFS from FTP)
proc XF_Screen {{force 0} {nolist 0}} {
    global xf ftp vdleft vfleft vdright vfright
    
    if {$xf(screen) == 0} {
	# normal fs to front
	#if {$xf(fsmode_left) == 4} {
	#    set s left
	#} {
	#    set s right
	#}
	set s $xf(bgact.side)
	set xf(screen.path) $xf(pathi$s)
	SetPaths $s $xf(pathfs) 1
	#set xf(screen.history) $xf(history_4)
	set xf(screen.dirs) [set vd$s]
	set xf(screen.files) [set vf$s]
	#set xf(screen.sel) $xf(selekted$s)
	#set xf(screen.side) $s
	set xf(fsmode_$s) 0
	set xf(screen.active) 1
	grid remove $xf(fuf).$s.evfs
	foreach z {left right} {
	    $xf(fuf).$z.copy config -state normal
	}
	NullSelect $s
	if {$nolist == 0} {
	    FileList $s $xf(pathi$s)
	    DefaultInfo $s
	}
    } {
	# FTP to front
	set s $xf(bgact.side)

	if {$xf(fsmode_$s) > 0} {
	    # other virtual mode active
	    MessageBox "Unable to return to FTP session while another virtual mode is open!" $s 
	    set xf(screen) 0
	    return
	}
	set xf(fsmode_$s) 4
	set vd$s $xf(screen.dirs)
	set vf$s $xf(screen.files)
	set xf(pathfs) $xf(pathi$s)
	#set xf(selekted$s) $xf(screen.sel)
	SetPaths $s $xf(screen.path) 1
	set xf(screen.active) 0
	grid $xf(fuf).$s.evfs
	foreach z {left right} {
	    $xf(fuf).$z.copy config -state disabled
	}
	NullSelect $s
	if {$force == 0} {
	    set xf(redraw$s) 1
	} {
	    set xf(ftp.update) 1
	}
	$xf(fuf).${s}list config -bg #eeeece
	#FileList $s $xf(pathi$s)
	UpdateListbox $s
	DefaultInfo $s
    }
}

# wrapper for similar calls
proc FO_VFS_bg {s paths patho cmd infostr} {
    global bg TMP

    set fileid [eval "bg_exec [list $cmd] \
	    {bg_handler 0 $s 0 $paths $patho} 1 1 $TMP(dir)"]
    set bg($fileid.stoptime) "RUNNING!"
    return [FO_WaitSem $fileid $infostr 1]
}
