# vim: set cindent expandtab ts=4 sw=4:
#
# Copyright (c) 1998-2005 Chi-Keung Ho. All rights reserved.
#
# This programe is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# Extmail - a high-performance webmail to maildir
# $Id$
package Ext::App::Folders;
use strict;
use Exporter;
use vars qw(@ISA @EXPORT $t0 $t1 $t2 $t3 $t4);
use vars qw($user_page_size);
@ISA = qw(Exporter Ext::App);
use Ext::App;
use Ext::MIME;
use Ext::DateTime;
use Ext::Utils;
use Ext::MailFilter;
use Ext::RFC822; # import date_fmt
use Ext::POP3;
use Ext::Abook; # to support friends display
use Ext::Storage::Maildir;
use Benchmark;
use vars qw(%lang_folders $lang_charset);
use Ext::Lang;
use Ext::Unicode;
sub init {
my $self = shift;
$self->SUPER::init(@_);
return unless($self->valid||$self->permit);
$self->add_methods(folders_list => \&folders_list);
$self->add_methods(messages_list => \&messages_list);
$self->add_methods(folders_mgr => \&folders_mgr);
$self->add_methods(messages_mgr => \&messages_mgr);
$self->{default_mode} = 'folders_list';
Ext::Storage::Maildir::init($self->get_working_path);
$t0 = new Benchmark;
$self->_initme;
if(my $FOLDER = $self->{query}->cgi('folder')) {
if (!valid_maildir($FOLDER)) {
$self->error($lang_folders{'err_name_invalid'});
}
}
$self;
}
sub _initme {
initlang($_[0]->userconfig->{lang}, __PACKAGE__);
$_[0]->{tpl}->assign( lang_charset => $lang_charset );
$_[0]->{tpl}->assign( \%lang_folders );
}
# varibles defination:
# edit => the dir to edit(delete or rename)
# foldername => the input field, for rename or mkdir
# oldfolder => original folder name
sub folders_mgr {
my $self = shift;
my $q = $self->{query};
my $dir = $q->cgi('foldername');
my $edit = $q->cgi('edit');
my $sid = $q->cgi('sid');
my $errmsg = undef;
my $filter = '';
my $utf8 = Ext::Unicode->new;
if ($edit) {
$self->{tpl}->assign(
MOD_EDIT => 1,
OLDFOLDER => str2url($edit),
OLDFOLDER_NAME => $utf8->decode_imap_utf7($edit),
);
}
$dir =~ s/\s+//g; # remove space
# return if no foldername specify
# return 0 unless($dir);
if ($q->cgi('__combine_mode')) {
eval {
my $func = $q->cgi('__combine_mode');
$self->$func;
};
if ($@) {
$self->error("$@");
return;
}
}
# XXX check is the request in filtering rules or not,
# if yes , stop processing the request because some
# rules need the folder, if change or delete, filtering
# programe will fail to process filter rule
if ($self->{sysconfig}->{SYS_MFILTER_ON}) {
$filter = new Ext::MailFilter;
}
if ($q->cgi('mkdir')) {
if (length($dir)<2) {
$self->error($lang_folders{err_tooshort});
return;
}
if (!foldername_ok($dir, 45)) {
$self->error($lang_folders{err_name_invalid});
return;
}
my $utf7_dir = $utf8->encode_imap_utf7($dir);
$errmsg = sprintf($lang_folders{err_mkdir}, $dir)
unless mk_maildir($utf7_dir);
} elsif ($q->cgi('rmdir')) {
my $del = $q->cgi('oldfolder');
$del = $utf8->encode_imap_utf7($utf8->decode_imap_utf7($del));
if (!$del or length($del)<2) {
$self->error($lang_folders{err_tooshort});
return;
}
if ($filter && (my $rv = $filter->dir_inrule($del))) {
$self->error(sprintf($lang_folders{err_rmdir_inrule}, $utf8->decode_imap_utf7($del),$rv));
return;
}
$errmsg = sprintf($lang_folders{err_rmdir}, $utf8->decode_imap_utf7($del))
unless rm_maildir($del);
re_calculate();
} elsif ($q->cgi('rename')) {
my $old = url2str($q->cgi('oldfolder'));
my $new = $dir;
# XXX FIXME decode it first then encode it, see explanation below
my $utf7_old = $utf8->encode_imap_utf7($utf8->decode_imap_utf7($old));
if (length($new)<2) {
$self->error($lang_folders{err_tooshort});
return;
}
if ($filter && (my $rv = $filter->dir_inrule($utf7_old))) {
$self->error(sprintf($lang_folders{err_rename_inrule}, $utf8->decode_imap_utf7($old), $rv));
return;
}
if (!foldername_ok($new, 45)) {
$self->error($lang_folders{err_name_invalid});
return;
}
# XXX FIXME decode it first then encode it, if we are handling utf7 encoded
# folder name, then we must decode it then encode it; if we are handling
# non-utf7 encoded folder name, then we still need to encode it again.
my $utf7_new = $utf8->encode_imap_utf7($new);
$errmsg = sprintf($lang_folders{err_rename}, $old)
unless mv_maildir($utf7_old, $utf7_new);
} elsif ($q->cgi('purge')) {
my $utf7_dir = $utf8->encode_imap_utf7($utf8->decode_imap_utf7($dir));
$errmsg = sprintf($lang_folders{err_purge}, $utf8->decode_imap_utf7($dir))
unless purge_maildir($utf7_dir);
re_calculate();
}
if (defined $errmsg) {
$self->error($errmsg);
return;
}
if($q->cgi('redirect')) {
$self->{tpl}->{noprint} = 1;
$self->redirect("?__mode=folders_list&sid=$sid");
}
}
sub messages_mgr {
my $self = shift;
my $q = $self->{query};
my $dir = $q->cgi('folder');
my $sid = $q->cgi('sid');
my $page = $q->cgi('page');
$self->{tpl}->{noprint} = 1;
if($q->cgi('delete')) {
my $a = $q->cgi_full_names;
my @pos = grep { s/^MOVE-// } @$a; # get pos id
set_bmsgs_delete($dir, @pos);
}
if($q->cgi('move')) {
my $a = $q->cgi_full_names;
my @pos = grep { s/^MOVE-// } @$a; # get pos id
# interface params: srcdir distdir @pos
set_bmsgs_move($dir, $q->cgi('distfolder'), @pos);
}
# must encode as url
$dir = str2url($dir);
$self->redirect("?__mode=messages_list&sid=$sid&folder=$dir&page=$page");
}
sub folders_list {
my $self = shift;
my $tpl = $self->{tpl};
$t1 = new Benchmark;
my @list = get_dirs_list;
$tpl->assign(FOLDERS_LIST=>1);
# pop3 here
my $pp;
if ($self->userconfig->{pop_on}) {
$pp= parse_pop3config();
}
if (scalar $pp && $self->{query}->cgi('chkpop')) {
my $timeout = $self->userconfig->{pop_timeout};
my $files = $self->userconfig->{pop_files};
my $obj = new Ext::POP3;
my $checked = 0;
for my $pop (@$pp) {
if ($pop->{active} ne 'on') {
next;
}
if ($obj->can_receive) {
$checked = 1 unless ($checked);
$obj->init(
user => $pop->{uid},
passwd => $pop->{passwd},
host => $pop->{host},
port => $pop->{port} || '110',
backup => $pop->{backup} eq 'on' ? 1 : 0,
timeout => $timeout,
max_files => $files,
);
my $rc = $obj->receive;
$obj->close;
}
}
$obj->finish; # kill the pop session
# XXX end of pop3
if ($checked) {
if (my $err = $obj->error) {
for (split(/\n/, $err)) {
$tpl->assign(
'LOOP_POP3ERR',
POP3ERR => ref $_ ? "@$_" : $_
);
}
} else {
$tpl->assign(POP3OK => 'POP3 Retrieve OK!');
}
}
}
foreach (@list) {
my $t0 = new Benchmark;
check_new($_); # check_new first
my $t1 = new Benchmark;
my ($diff) = (timestr(timediff($t1, $t0)) =~ /= (.*) CPU/);
}
$t2 = new Benchmark;
}
sub messages_list {
my $self = shift;
my $tpl = $self->{tpl};
my $q = $self->{query};
my $utf8 = Ext::Unicode->new;
my %abook = map { lc $_->[1] => ( $_->[1] ? 1: 0 ) } @{Ext::Abook->new('abook.cf')->dump};
$tpl->assign(MESSAGES_LIST=>1);
my $fd = $q->cgi("folder");
my $sid = $self->{sid};
my $list = undef;
my ($nonext, $noprev, $nofirst, $nolast);
my $prefix = $ENV{MAILDIR}.'/'.($fd eq 'Inbox'?'.':".$fd").'/cur';
$t1 = new Benchmark;
# Sort cache on demand
my $sort_order = $self->userconfig->{sort};
if($q->cgi('resort')) {
$sort_order = name2sort($q->cgi('resort'));
rebuild_msgs_cache($fd, $sort_order);
}else {
set_msgs_cache($fd, $sort_order);
}
# show sort order, using i18n XXX
# Internal mechanism: check $order for by_xxx_rev <--, if found, set
# flag_rev true, then strip out sort type(exclude _rev), then template
# can easily identify which sort type is currently used and whether
# in reverse mode or not
my $order = sort2name(get_sortorder($fd)); # Dt, rSz etc...
$tpl->assign(SORT_ORDER => $lang_folders{$order});
my($flag_rev) = ($order =~/_rev/?1:0);
$order =~s/_rev//; # this only indicate sort type, not asc/desc
$tpl->assign(
'flag_'.$order.'_rev' => $flag_rev,
'flag_'.$order => 1,
CURPAGE => $q->cgi('page'),
);
$t2 = new Benchmark;
($list, $nonext) = $self->paging($self, $fd, $q->cgi('page'));
$tpl->assign(FOLDER => str2url($fd)); # XXX str2url
$tpl->assign(R_FOLDER => $fd); # raw folder name, XXX must exist
if($fd =~ /^(Drafts|Sent)$/) {
$tpl->assign(REV_FROM => 1);
# XXX change link to edit drafts for user
$tpl->assign(FOLDER_DRAFTS => 1) if ($fd eq 'Drafts');
}
$tpl->assign(HAVEMSGLIST => 1) if (keys %$list);
foreach my $pos (sort {$a<=>$b} keys %$list) {
my $file = $list->{$pos}->{FILENAME};
my ($size) = $list->{$pos}->{SIZES};
my $flag_att = ($file =~ /:.*(A).*/) ? 1:0;
my $flag_new = ($file =~ /:.*(S).*/) ? 0:1;
# XXX advoid special char corupt the html output, caution:
# do not use my($var1, $var2..) = xxx, it will fall into a
# trap. If one of the values is "" or null, then the later
# value will replace it, causing value mismatch XXX
for (qw(FROM SUBJECT)) {
next unless $list->{$pos}->{$_};
next unless $list->{$pos}->{$_} =~ /=\?[^?]*\?[QB]\?[^?]*\?=/;
$list->{$pos}->{$_} =~ s/(\?=)\s+(=\?)/$1$2/g;
}
# die $list->{$pos}->{FROM} if length $list->{$pos}->{FROM} > 20;
my $addr = rfc822_addr_parse(decode_words_utf8($list->{$pos}->{FROM}));
my $from = $addr->{name}; # get the name part only
my $subject = decode_words_utf8($list->{$pos}->{SUBJECT});
my $date = $list->{$pos}->{DATE};
my $timezone = $self->userconfig->{timezone};
my $sjchar;
TRY: {
my $subject = decode_words($list->{$pos}->{SUBJECT});
$subject =~ s/\s+//; # remove space
my $c = charset_detect($subject);
if ($c =~ /^(windows-1252|iso-8859-)/ && length $subject < 6) {
$sjchar = charset_detect(decode_words($list->{$pos}->{FROM}));
} else {
$sjchar = $c;
}
}
$from = iconv($from, $sjchar, 'utf-8') if charset_detect($from) ne 'utf-8';
$subject = iconv($subject, $sjchar, 'utf-8') if charset_detect($subject) ne 'utf-8';
# truncate to a limit size, or long line will break the
# view of html, but truncate function should rewrite to beter
# display characters
my $tr = $self->get_screen($self->userconfig->{screen});
if ($tr->[0] && length $from > $tr->[1]) {
$from=substr($from, 0, $tr->[0])."...";
}
if($tr->[1] && length $subject > $tr->[1]) {
$subject=substr($subject,0, $tr->[1])."...";
}
$tpl->assign(
'LOOP_SUBLIST', # Must be quote, or die under strict
POS => $pos,
FILE => $file,
SUBJECT => html_escape($subject=~/\S+/ ? $subject : $lang_folders{notitle} || 'No Title'),
FROM => html_escape($from),
DATE => date_fmt('%s, %s %s:%s', $date), # XXX FIXME
SHORTDATE => dateserial2str(datefield2dateserial($date), $timezone, 'auto','stime', 12),
SIZE => $size,
FATT => $flag_att,
FNEW => $flag_new,
FROMCONTACT => $abook{$addr->{addr}} ? 1 : 0,
);
}
# page index
my $inf = get_dir_cnt($fd);
my $usercfg = $self->userconfig();
# global varible initialize
$user_page_size = $usercfg->{page_size};
# jklin use ceil() from POSIX, here we use myceil() from Utils.pm
my $total_pages = myceil(($inf->{new}+$inf->{seen})/$user_page_size);
for(my $i = 1; $i <= $total_pages; $i++) {
$tpl->assign(
'LOOP_PAGES',
PAGE_VALUE => $i-1,
PAGE_TEXT => "$i / $total_pages",
IS_SELECTED => ($i-1)==$q->cgi('page')?1:0
);
}
$t3 = new Benchmark;
my $prev = ($q->cgi("page") ? $q->cgi("page") -1 : 0);
my $next = ($q->cgi("page") ? $q->cgi("page") +1 : 1);
my $first = 0;
my $last = $total_pages-1;
if($q->cgi('page') eq $prev) {
$noprev = 1;
}
if(!$q->cgi('page')) {
$noprev = 1;
}
$nofirst = $q->cgi('page') <= 0?1:0;
$nolast = ($q->cgi('page') >= ($total_pages-1)) || ($total_pages<=1)?1:0;
my $curdir = $q->cgi('folder');
# setting up default conversion charset
$utf8->set_charset($lang_charset);
$tpl->assign(
FOLDER2=> str2url($curdir), # XXX should str2url
FOLDER2_NAME => $lang_folders{$curdir} ? $lang_folders{$curdir} : $utf8->decode_imap_utf7($curdir),
PREV => $prev,
NEXT => $next,
FIRST => $first,
LAST => $last,
HAVE_PREV => $noprev?0:1,
HAVE_NEXT => $nonext?0:1,
HAVE_FIRST => $nofirst?0:1,
HAVE_LAST => $nolast?0:1,
NEED_PAGING => $total_pages <=1?0:1,
);
my @list = get_dirs_list;
for(@list) {
next if($fd eq $_); # ignore the current folder XXX
# caution, template currently not support same VAR resuing
# in the loop, ouch :-(
my $name = $lang_folders{$_};
$tpl->assign(
'LOOP_FOLDERS',
DISTFOLDER => $_,
DISTNAME => $name?$name:$utf8->decode_imap_utf7($_));
}
# $self->show_curquota;
}
sub pre_run { 1 }
sub post_run {
my $template = $_[0]->{query}->cgi('screen') || 'folders.html';
# dirty hack, to fallback original working path, ouch :-(
reset_working_path();
$_[0]->{tpl}->process($template);
$_[0]->{tpl}->print;
}
sub paging {
my $self = shift;
my $usercfg = $self->userconfig();
# global varible initialize
$user_page_size = $usercfg->{page_size};
my($obj, $dir, $page) = @_;
if (!$page) { $page = 0; }
my ($c,$nomore) = get_msgs_cache(
$dir,
$user_page_size,
$user_page_size*$page
);
($c, $nomore);
}
sub sort2name {
my $method = shift;
my %map = (
Dt => 'by_date',
Ts => 'by_time',
Sz => 'by_size',
Fr => 'by_from',
Sj => 'by_subject',
Fs => 'by_status',
rDt => 'by_date_rev',
rTs => 'by_time_rev',
rSz => 'by_size_rev',
rFr => 'by_from_rev',
rSj => 'by_subject_rev',
rFs => 'by_status_rev'
);
$map{$method} || 'by_time'; # if null, try by_time
}
sub name2sort {
my $name = shift;
my %map = (
'by_date' => 'Dt',
'by_time' => 'Ts',
'by_size' => 'Sz',
'by_from' => 'Fr',
'by_subject' => 'Sj',
'by_status' => 'Fs',
'by_date_rev' => 'rDt',
'by_time_rev' => 'rTs',
'by_size_rev' => 'rSz',
'by_from_rev' => 'rFr',
'by_subject_rev' => 'rSj',
'by_status_rev' => 'rFs'
);
$map{$name} || 'Ts'; # if null, try Ts
}
sub perf_time {
my $tpl = shift->{tpl};
if($t1 and $t0) {
$tpl->assign(TIME1=> timestr(timediff($t1, $t0)));
}
if($t2 and $t1) {
$tpl->assign(TIME2=> timestr(timediff($t2, $t1)));
}
if($t3 and $t2) {
$tpl->assign(TIME3=> timestr(timediff($t3, $t2)));
}
if($t4 and $t3) {
$tpl->assign(TIME4=> timestr(timediff($t4, $t3)));
}
}
sub DESTORY {
}
1;
syntax highlighted by Code2HTML, v. 0.9.1