# -*-Perl-*- ################################################################ ### ### Folder.pm ### ### Author: Internet Message Group ### Created: Apr 23, 1997 ### Revised: Jul 4, 2004 ### my $PM_VERSION = "IM::Folder.pm version 20031028(IM146)"; package IM::Folder; require 5.003; require Exporter; use IM::Config qw(expand_path context_file inbox_folder folder_mode usetouchfile touchfile); use IM::Util; use integer; use strict; use vars qw(@ISA @EXPORT); @ISA = qw(Exporter); @EXPORT = qw(cur_folder set_cur_folder folder_info message_list message_number message_range message_name get_message_paths create_folder touch_folder chk_folder_existance chk_msg_existance get_impath); # # Mail folder related routines. # sub cur_folder() { my $folder; local(*IN); return inbox_folder() if (! -f context_file()); $folder = ''; im_open(\*IN, '< ' . context_file()) || im_die("can't open context file.\n"); while () { chomp; if (/^CurrentFolder[:=]\s*(\S+)$/) { $folder = $1; } } close(IN); return $folder; } sub set_cur_folder($) { my($folder) = @_; local(*IN, *OUT); my($buf); $buf = ''; if (-f context_file()) { im_open(\*IN, '<' . context_file()) || im_die("can't open context file.\n"); while () { chomp; next if (/^CurrentFolder[:=]\s*(\S+)$/); $buf .= $_ . "\n"; } close(IN); } im_open(\*OUT, '>' . context_file()) || im_die("can't open context file.\n"); print OUT $buf; print OUT "CurrentFolder=$folder\n"; close(OUT); } sub folder_info($) { my($folder) = @_; local(*DIR); my(@allfiles, $filecnt, $numfilecnt, $min, $max); opendir(DIR, &expand_path($folder)) || im_die("can't open $folder.\n"); @allfiles = grep(!/^\./, readdir(DIR)); $filecnt = scalar(@allfiles); @allfiles = grep(/^\d+$/, @allfiles); $numfilecnt = scalar(@allfiles); $min = (sort {$a <=> $b} @allfiles)[0]; $max = (sort {$b <=> $a} @allfiles)[0]; closedir(DIR); return ($filecnt, $numfilecnt, $min, $max); } sub message_list($) { my($folder_dir) = @_; my @filesinfolder; opendir(DIR, $folder_dir) || im_die("can't open $folder_dir.\n"); @filesinfolder = sort {$a <=> $b} grep(/^\d+$/, readdir(DIR)); closedir(DIR); return @filesinfolder; } sub message_number($$;@) { my($folder, $number, @filesinfolder) = @_; my($folder_dir, $offset, $max, $min); # simple case: digits if ($number !~ /\D/) { return $number; } # get folder $folder = cur_folder if ($folder eq ''); $folder_dir = expand_path($folder); return '' if (! -d $folder_dir); @filesinfolder = message_list($folder_dir) if (scalar(@_) == 2); if (scalar(@filesinfolder) == 0) { if ($number eq 'new') { $number = '1'; while (-e "$folder_dir/$number" || -e "$folder_dir/.$number.dir") { $number++; } return $number; } else { return ''; } } $min = $filesinfolder[0]; $max = $filesinfolder[$#filesinfolder]; # items that need reverse ordered list if ($number eq 'last') { return $max; } if ($number eq 'first') { return $min; } if ($number eq 'new') { $number = $max + 1; while (-e "$folder_dir/$number" || -e "$folder_dir/.$number.dir") { $number++; } return $number; } if ($number eq 'next' || $number eq 'prev') { $offset = ($number eq 'prev') ? -1 : +1; $number += $offset; while ($min <= $number && $number <= $max) { return $number if (-f "$folder_dir/$number"); $number += $offset; } } return ''; } sub message_range($$@) { my($folder, $range, @filesinfolder) = @_; my $range_regexp = '\d+|first|last|next|prev'; $folder = cur_folder if ($folder eq ''); my $folder_dir = expand_path($folder); if ($range eq 'all') { $range = 'first-last'; } if ($range =~ /^($range_regexp|new)-($range_regexp|new)$/) { my($start, $end) = ($1, $2); $start = message_number($folder, $start, @filesinfolder); $end = message_number($folder, $end, @filesinfolder); if ($start eq '' || $end eq '' || $start > $end) { return (); } else { return grep($start <= $_ && $_ <= $end, @filesinfolder); } } elsif ($range =~ /^($range_regexp):([+-]?)(\d+)$/) { my($start, $dir, $n) = ($1, $2, $3); if ($dir eq '') { $dir = ($start eq 'last') ? '-' : '+'; } $start = message_number($folder, $start, @filesinfolder); return $range if ($start eq ''); if ($dir eq '+') { @filesinfolder = grep($start <= $_, @filesinfolder); splice(@filesinfolder, $n) if $n < scalar(@filesinfolder); } else { @filesinfolder = grep($_ <= $start, @filesinfolder); splice(@filesinfolder, 0, @filesinfolder - $n) if $n < scalar(@filesinfolder); } return @filesinfolder; } else { return message_number($folder, $range); } } sub message_name($$) { my($folder, $number) = @_; $number = &message_number($folder, $number); if ($number eq '') { return ''; } else { return &expand_path($folder) . '/' . $number; } } sub get_message_paths($@) { my($folder, @messages0) = @_; # local @messages0? my($i, @messages, @x); # local(@messages, @x);? my $folder_dir = &expand_path($folder); # no message specified: # just print the path to the folder, and quit. if (scalar(@messages0) == 0) { return ($folder_dir); } # messages specified. # print the path to the message. if (! -d $folder_dir) { $@ = "no such folder $folder"; return (); } # ad hoc but fast if (scalar(@messages0) == 1 && $messages0[0] eq 'new') { local(*MDIR); my($i); my $max = "0"; opendir(MDIR, $folder_dir) || im_die("can't open $folder.\n"); while (defined($i = readdir(MDIR))) { $max = $i if ($max < $i and $i =~ /^\d+$/); } $max++; closedir(MDIR); return "$folder_dir/$max"; } my @filesinfolder = message_list($folder_dir); @messages = @x = (); foreach $i (@messages0) { if ((@x = &message_range($folder, $i, @filesinfolder)) eq '') { $@ = "message $i out of range"; return (); } push(@messages, @x); } grep($_ = "$folder_dir/$_", @messages); } sub create_folder($) { my $folder = shift; my $path = &expand_path($folder); return 0 if (-d $path); my $p = ''; my $subdir; foreach $subdir (split('/', $path)) { if ($p eq '' && $subdir =~ /^\w:$/) { $p = $subdir; next; } $p .= "/$subdir"; if ($> != 0) { $p =~ /(.+)/; # may be tainted $p = $1; # clean up } unless (-d $p) { # im_debug("Creating directory: $p\n") # if (&debug('folder')); unless (mkdir($p, &folder_mode(0))) { im_err("can't create directory $p ($!)\n"); return -1; } } } return 0; } sub touch_folder($) { if (&usetouchfile()) { my($dir) = shift; $dir =~ s/\/\d+$//; $dir = &expand_path($dir); my($file) = ($dir . "/" . &touchfile()); im_open(\*OF,">$file"); print OF "touched by IM."; close(OF); } elsif (&os2p) { my($dir) = shift; $dir =~ s/\/\d+$//; $dir = &expand_path($dir); my $now = time; # XXX utime ($now, $now, $dir); } } ## ## Check folder existance. ## sub chk_folder_existance(@) { my @folders = @_; my $path; im_debug("chk_folder_existance: folder: @folders\n") if (&debug('all')); foreach (@folders) { next if /^[%-]/; # skip IMAP and News folders $path = get_impath($_); if (-e $path) { im_die "folder $_ is not writable. (Nothing was refiled.)\n" if (! -w $path); } else { if (create_folder($path) == 0) { im_warn "created folder $_.\n"; } else { im_die "cannot create folder $_. (Nothing was refiled.)\n"; } } } im_debug("chk_folder_existance: OK.\n") if (&debug('all')); } sub chk_msg_existance($@) { my $folder = shift; my @paths = get_impath($folder, @_); im_debug("chk_msg_existance: folder: $folder msg: @_\n") if (&debug('all')); foreach (@paths) { if (! -f $_) { im_die "message specification error in $folder. (Nothing was refiled.)\n"; } } im_debug("chk_msg_existance: OK.\n") if (&debug('all'));; } sub get_impath($@) { my $folder = shift; my @msgs = @_; my @paths; im_debug("impath: folder: $folder msgs: @msgs\n") if (&debug('all'));; @paths = get_message_paths($folder, @msgs); im_debug("impath: paths: @paths\n") if (&debug('all'));; return wantarray ? @paths : $paths[0]; } 1; __END__ =head1 NAME IM::Folder - mail/news folder handler =head1 SYNOPSIS use IM::Folder; $current_folder_name = &cur_folder(); &set_cur_folder($new_current_folder_name); ($number_of_files, $number_of_message_files, $minimum_message_number, $maximum_message_number) = &folder_info($folder_name); $message_number = &message_number($message_number_or_name); @message_number_array = &message_range($message_range_string); $message_file_path = &message_name($folder_name, $message_number); =head1 DESCRIPTION The I module handles mail/news message folders. This modules is provided by IM (Internet Message). =head1 EXAMPLES &cur_folder(); results "+inbox" &set_cur_folder("+inbox"); ($a, $b, $c, $d) = &folder_info("+inbox"); results (10, 3, 1, 3) &message_number("+inbox", "cur"); results 3 &message_range("+inbox", "1-3"); results (1, 2, 3) &message_name("+inbox", "3"); results "/usr/home/itojun/Mail/inbox/3" =head1 COPYRIGHT IM (Internet Message) is copyrighted by IM developing team. You can redistribute it and/or modify it under the modified BSD license. See the copyright file for more details. =cut ### Copyright (C) 1997, 1998, 1999 IM developing team ### All rights reserved. ### ### Redistribution and use in source and binary forms, with or without ### modification, are permitted provided that the following conditions ### are met: ### ### 1. Redistributions of source code must retain the above copyright ### notice, this list of conditions and the following disclaimer. ### 2. Redistributions in binary form must reproduce the above copyright ### notice, this list of conditions and the following disclaimer in the ### documentation and/or other materials provided with the distribution. ### 3. Neither the name of the team nor the names of its contributors ### may be used to endorse or promote products derived from this software ### without specific prior written permission. ### ### THIS SOFTWARE IS PROVIDED BY THE TEAM AND CONTRIBUTORS ``AS IS'' AND ### ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ### IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ### PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE TEAM OR CONTRIBUTORS BE ### LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ### CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ### SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR ### BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ### WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE ### OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN ### IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.