2017年6月29日木曜日

FreeBSD で fine-uploader を使ってみた(その3)

 前々回,FreeBSD で fine-uploader を使ってみた(その1)として fine-uploader によるサーバーへのファイルのアップロードについて書いた。 また,前回FreeBSD で fine-uploader を使ってみた(その2)として Endpoint ルーチンについて書いた。
 今回は chunking(分割送信)の際の結合ルーチンについて書こう。 結合ルーチンは通常の Endpoint ルーチンとは別に用意された Endpoint ルーチンであり,分割送信が完了した時(全ての分割ファイルの送信が完了した時)に呼ばれる。 そのため,中身は通常の Endpoint ルーチンとほぼ同じに見える。

 以下に chunking の結合用の Endpoint ルーチンのソースを書いておく。 説明はソースの後で書こう。
#!/usr/bin/perl
#
use CGI::Carp qw(fatalsToBrowser);
use Digest::MD5;
use CGI;
my $IN = new CGI;
# ================================================================
$ext_check = 1; # Do extension check
$original_filename = 1; # use original filename (Not a random name)
$unique_file_max_number = 10; # maximum number for same file name
# ================================================================
print STDERR "\n"."<<<Chunk concatenate process>>>"."\n";

my $uploaddir = '../uploadFiles/';
my $uploadtmpdir = '../uploadFiles/';
my $maxFileSize = 20971520; # 20 MB

my $file;
if ($IN->param('POSTDATA')) { $file = $IN->param('POSTDATA'); }
                       else { $file = $IN->upload('qqfile'); }
my $qquuid = $IN->param('qquuid');
my $qqtotalfilesize = $IN->param('qqtotalfilesize');
my $qqtotalparts = $IN->param('qqtotalparts');
my $qqchunksize = $IN->param('qqchunksize');
my $qqpartbyteoffset = $IN->param('qqpartbyteoffset');
my $qqpartindex = $IN->param('qqpartindex');

my $filename4store = $IN->param('qqfilename');
if ($filename4store eq '') { $filename4store = $file; }

# make a random filename, and we guess the file type later on...
my $name = Digest::MD5::md5_base64(rand);
$name =~ s/\+/_/g;
$name =~ s/\//_/g;

# file head の取り出しと,拡張子のチェック
my $filehead, $ext, $extUC, $filename;
my $error = 0;
if ($filename4store =~ /(.*)\.(.*)/) {
    $filehead = $1;  $ext = $2;
    if ($ext_check == 1) {
        $extLC = lc($ext);
        if ($extLC !~ /jpg|jpeg|gif|png|zip|7z/) { $error =1; } # Invalid extension type error
    }
}

# オリジナルファイル名を使う際には,同じ名前があるかを調べてから書き込む。
# 上書きしない場合で同一名があれば,ファイル名に数字を付加する。
if ($original_filename == 1) {
    my $ii = 0;
    my $filename_work = $uploaddir.$filehead.'.'.$ext;
    if (-f $filename_work) {
# 同一名の時には後ろに番号を付加する
        while ((-f $filename_work) and ($ii<$unique_file_max_number)) {
            $ii++;
            $filename_work = $uploaddir.$filehead.'-'.$ii.'.'.$ext;
        }
        $filename = $filehead.'-'.$ii.'.'.$ext;
    } else { $filename = $filehead.'.'.$ext; }
} else {
    $filename = $name.'.'.$ext; # random file name
}

# ファイルの結合
my $check_size;
if ($error == 0) {
# ---------------------------------------------
# chunking concatenate process
    system("/usr/bin/touch ".$uploaddir.$filename);
    for ($ii=0; $ii<$qqtotalparts; $ii++) {
        system("/bin/cat ".$uploaddir.$filename." ".$uploadtmpdir.$filename.".tmp".$ii.".".$filename4store." > ".$uploaddir."qqtemp");
        rename($uploaddir."qqtemp", $uploaddir.$filename);
        unlink($uploadtmpdir.$filename.".tmp".$ii.".".$filename4store);
    }
    chmod 0664,"$uploaddir$filename";
# ---------------------------------------------
    $check_size = -s "$uploaddir$filename";
    if    ($check_size < 1)            { $error = 2; } # file empty error
    elsif ($check_size > $maxFileSize) { $error = 3; } # Too big error

    print STDERR qq|Main filesize: $check_size   Max Filesize: $maxFileSize \n|;
} # if ($error == 0)

my $currentTimeStr = &GetDateTimeStr();
print $IN->header();
if ($error == 0) {
    print qq|{"success":true,"filename":"$filename","size":"$check_size","endtime":"$currentTimeStr" }|;
    print STDERR "File has been successfully uploaded.\n";
} elsif ($error == 2) {
    print qq|{"success":false,"error":"File is empty" }|;
    print STDERR "File is EMPTY !!\n";
    print STDERR "file has been NOT been uploaded... \n";
} elsif ($error == 3) {
    print qq|{"success":false,"error":"File is too large" }|;
    print STDERR "File size is TOO LARGE !!\n";
    print STDERR "file has been NOT been uploaded... \n";
} else {
    print qq|{"success":false,"error":"Invalid file type"}|;
    print STDERR "Invalid file extension ERROR !!\n";
    print STDERR "file has been NOT been uploaded... \n";
} # if $error == 0

print STDERR '       Date Time = "'.$currentTimeStr.'"'."\n";
print STDERR '      upload_dir = "'.$uploaddir.'"'."\n";
print STDERR '  filename(orig) = "'.$file.'"'."\n";
print STDERR 'filename(edited) = "'.$filename4store.'"'."\n";
print STDERR 'filename(stored) = "'.$filename.'"'."\n";
print STDERR '          qquuid = "'.$qquuid.'"'."\n";
print STDERR ' qqtotalfilesize = "'.$qqtotalfilesize.'",  qqchunksize ="'.$qqchunksize.'",  qqpartbyteoffset ="'.$qqpartbyteoffset.'",  qqtotalparts ="'.$qqtotalparts.'",  qqpartindex ="'.$qqpartindex.\
'"'."\n";
print STDERR "\n";

exit;
# ======================================================================
sub GetDateTimeStr {
    my(@GTS_date_array) = @_;
    if ($GTS_date_array[1] eq '') {
        @GTS_date_array = localtime(time);
    }
    my $dum=($GTS_date_array[5]+1900)."/".&ZeroPadding($GTS_date_array[4]+1)."/".&ZeroPadding($GTS_date_array[3])
        .' '.&ZeroPadding($GTS_date_array[2]).":".&ZeroPadding($GTS_date_array[1]).":".&ZeroPadding($GTS_date_array[0]);
    return $dum;
}

sub ZeroPadding {
    my($dum)=sprintf("%d",$_[0]);
    $dum="00".$dum;
    $dum=substr($dum,(length($dum)-2));
    return $dum;
}
 これは,前回FreeBSD で fine-uploader を使ってみた(その2)の Endpoint ルーチンと見た目はそっくりである。 違うのはファイルの保存はせず,すでにアップロードされた分割ファイルの結合を行っている点ぐらいである。 具体的には,元の Endpoint ルーチンの以下の2箇所を結合ルーチンに置き換えている。
# ---------------------------------------------
# $qqpartindex がカラでない時は,chunking されたファイルの一部分と解釈する
if ($qqpartindex ne '') {
    $filename = $filename.'.tmp'.$qqpartindex.'.'.$filename4store; # like 'P114025-2.JPG.tmp0.P114025.JPG'
    $uploaddir = $uploadtmpdir; # directory の切替え
}
# ---------------------------------------------

# ---------------------------------------------
# 実際のファイルの書き込み処理
    binmode(WRITEIT);
    open(WRITEIT, ">$uploaddir$filename") or die "Cant write to $uploaddir$filename. Reason: $!";
    if ($IN->param('POSTDATA')) {
        print WRITEIT $file;
    } else {
        while (<$file>) { print WRITEIT; }
    }
    close(WRITEIT);
# ---------------------------------------------

 以下が置換えた結合ルーチンである。
# ---------------------------------------------
# chunking concatenate process
    system("/usr/bin/touch ".$uploaddir.$filename);
    for ($ii=0; $ii<$qqtotalparts; $ii++) {
        system("/bin/cat ".$uploaddir.$filename." ".$uploadtmpdir.$filename.".tmp".$ii.".".$filename4store." > ".$uploaddir."qqtemp");
        rename($uploaddir."qqtemp", $uploaddir.$filename);
        unlink($uploadtmpdir.$filename.".tmp".$ii.".".$filename4store);
    }
    chmod 0664,"$uploaddir$filename";
# ---------------------------------------------
 何の事はない,単にファイルを結合して,保存された分割ファイルを削除している,だけである。 あまり細かい解説はいらないと思う。

 以下が /var/log/httpd-error.log に記載された作業ログである。
<<<No chunk process>>>
Main filesize: 989516
File has been successfully uploaded.
       Date Time = "2017/06/xx 22:15:50"
      upload_dir = "../uploadFiles/"
  filename(orig) = "blob"
filename(edited) = "cell_picts.zip"
filename(stored) = "cell_picts.zip.tmp8.cell_picts.zip"
          qquuid = "0ad6e572-1d12-4e8b-bfa4-1982700b66fb"
 qqtotalfilesize = "17126732",  qqchunksize ="989516",  qqpartbyteoffset ="16137216",  qqtotalparts ="9",  qqpartindex ="8"


<<<No chunk process>>>
Main filesize: 2017152
File has been successfully uploaded.
       Date Time = "2017/06/xx 22:15:50"
      upload_dir = "../uploadFiles/"
  filename(orig) = "blob"
filename(edited) = "cell_picts.zip"
filename(stored) = "cell_picts.zip.tmp6.cell_picts.zip"
          qquuid = "0ad6e572-1d12-4e8b-bfa4-1982700b66fb"
 qqtotalfilesize = "17126732",  qqchunksize ="2017152",  qqpartbyteoffset ="12102912",  qqtotalparts ="9",  qqpartindex ="6"


<<<No chunk process>>>
Main filesize: 2017152
File has been successfully uploaded.
       Date Time = "2017/06/xx 22:15:50"
      upload_dir = "../uploadFiles/"
  filename(orig) = "blob"
filename(edited) = "cell_picts.zip"
filename(stored) = "cell_picts.zip.tmp7.cell_picts.zip"
          qquuid = "0ad6e572-1d12-4e8b-bfa4-1982700b66fb"
 qqtotalfilesize = "17126732",  qqchunksize ="2017152",  qqpartbyteoffset ="14120064",  qqtotalparts ="9",  qqpartindex ="7"


<<<Chunk concatenate process>>>
Main filesize: 17126732
File has been successfully uploaded.
       Date Time = "2017/06/xx 22:15:52"
      upload_dir = "../uploadFiles/"
  filename(orig) = ""
filename(edited) = "cell_picts.zip"
filename(stored) = "cell_picts.zip"
          qquuid = "0ad6e572-1d12-4e8b-bfa4-1982700b66fb"
 qqtotalfilesize = "17126732",  qqchunksize ="",  qqpartbyteoffset ="",  qqtotalparts ="9",  qqpartindex =""
 似たようなログが並んでいるが,よく見ると qqpartindex が 8,6,7 と並んでいる。 元のファイルを9個に分割して(0番から8番まで)送信した最後の3個と,それを結合した際のログである。 ログの最初の3個(8番,6番,7番)は,前回記載の Endpoint ルーチン(No chunk process)でそれぞれ受け取ったものである。 順番が番号順でないのは同時送信しているからかもしれない(8番が最後なのでファイルサイズが少し小さい)。 特徴としては,送信時のファイル名が「blob」になっている。これは分割送信を意味している。 一方,保存ファイル名は作業の都合で長くなっている。 また,qqchunksize や qqpartbyteoffset 等は,それぞれの分割に応じた値が入っている。

 かたや最後のログは結合ルーチンのログである。 これはポータルサイトで,chunking が終わった際に呼び出されたもので,qqpartindex や qqchunksize,qqpartbyteoffset がカラとなっている。 一方,ファイルサイズは qqtotalfilesize で与えられたファイルの大きさに一致し,結合が成功したことを示している。

 これで fine-uploader を使ったファイルアップロードの話は終わりである。 私の場合,送信がうまくいった場合,圧縮ファイルなら展開し,画像ファイルならサムネイルを作って一覧表示させるようにしている。 しかし,それはかなりごちゃごちゃしてしまうので,ここでは書かないでおこう。

0 件のコメント: