<?php
/**
 * 文件处理核心类
 *
 * @author     呼吸二氧化碳 <jonwang@myqee.com>
 * @category   Core
 * @package    Classes
 * @copyright  Copyright (c) 2008-2016 myqee.com
 * @license    http://www.myqee.com/license.html
 */
class Core_File
{
    /**
     * 已知的系统目录
     *
     * @var array
     */
    protected static $sys_dir = array
    (
        DIR_CORE,
        DIR_DATA,
        DIR_CACHE,
        DIR_LIBRARY,
        DIR_LOG,
        DIR_TEMP,
        DIR_WWWROOT,
        DIR_TEAM_LIBRARY,
        DIR_PROJECT,
        DIR_MODULE,
        DIR_DRIVER,
        DIR_SYSTEM,
        DIR_ASSETS,
    );

    /**
     * 允许读写操作的目录
     *
     * @var array
     */
    public static $dir = array
    (
        'data'    => DIR_DATA,
        'cache'   => DIR_CACHE,
        'wwwroot' => DIR_WWWROOT,
        'log'     => DIR_LOG,
        'temp'    => DIR_TEMP,
        'assets'  => DIR_ASSETS,
    );

    /**
     * 文件是否存在
     *
     * @param string $file
     */
    public static function is_file($file, $storage = 'default')
    {

    }

    /**
     * 创建一个文件，多服务器可以自动同步
     *
     * @param string $file
     * @param $data
     * @param $flags
     * @param $context
     * @param string $storage 物理存储组，不传则为默认
     * @return boolean
     */
    public static function create_file($file, $data ,$flags = null, $context = null, $storage = 'default')
    {
        $info = File::check_and_get_path($file);

        if (File::can_do_run($storage))
        {
            # 系统内部运行时或小于等于1台服时执行

            # 系统禁用了写入功能
            if (Core::is_file_write_disabled())return false;

            $dir = substr($file, 0, (int)strrpos(str_replace('\\', '/', $file), '/'));

            if ($dir && !is_dir($dir))
            {
                # 没有文件夹先则创建
                File::create_dir($dir);
            }

            if (false !== @file_put_contents($file, $data, $flags, $context))
            {
                @chmod($file, 0666);

                return true;
            }
            else
            {
                if (IS_DEBUG)Core::debug()->warn('create file error:'. Core::debug_path($file));
                return false;
            }
        }
        else
        {
            return File::call_http_host($storage, 'file/create_file', $info[0], $info[1], $data, $flags, $context);
        }
    }

    /**
     * 循环建立目录，多服务器可以自动同步
     *
     * @param string $dir 待创建的文件夹
     * @param boolean $auto_create_default_file 新创建的文件夹，是否自动创建空默认页
     * @param string $storage 物理存储组，不传则为默认
     * @return boolean true/false
     */
    public static function create_dir($dir, $auto_create_default_file = true , $storage = 'default')
    {
        $info = File::check_and_get_path($dir);

        if (File::can_do_run($storage))
        {
            if (!is_dir($dir))
            {
                if (substr($dir,0,strlen(DIR_SYSTEM))==DIR_SYSTEM)
                {
                    $temp = explode('/', str_replace('\\', '/', substr($dir,strlen(DIR_SYSTEM))));
                    $cur_dir = DIR_SYSTEM;
                }
                else
                {
                    $temp = explode('/', str_replace('\\', '/', $dir));
                    $cur_dir = '';
                }
                for($i = 0; $i < count($temp); $i++)
                {
                    $cur_dir .= $temp[$i] .'/';
                    if (!is_dir($cur_dir))
                    {
                        if (@mkdir($cur_dir, 0755))
                        {
                            if ($auto_create_default_file)File::create_file($cur_dir .'index.html', ' ');
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
            }

            return true;
        }
        else
        {
            return File::call_http_host($storage, 'file/create_dir', $info[0], $info[1], $auto_create_default_file);
        }
    }

    /**
     * 删除文件，支持多个文件，多服务器可以自动同步
     *
     * @param string/array $file
     * @param string $storage 物理存储组，不传则为默认
     * @return boolean
     */
    public static function unlink($file, $storage = 'default')
    {
        $info = File::check_and_get_path($file);

        if (File::can_do_run($storage))
        {
            try
            {
                if (is_array($file))
                {
                    $rs = true;
                    foreach ($file as $f)
                    {
                        if (is_file($f))
                        {
                            if (!unlink($f))
                            {
                                $rs = false;
                                break;
                            }
                        }
                    }
                    return $rs;
                }
                else
                {
                    if (is_file($file))
                    {
                        return unlink($file);
                    }
                    elseif (is_dir($file))
                    {
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                }
            }
            catch (Exception $e)
            {
                return false;
            }
        }
        else
        {
            return File::call_http_host($storage, 'file/unlink', $info[0], $info[1]);
        }
    }

    /**
     * 循环删除目录下的所有目录和文件，多服务器可以自动同步
     *
     * @param string $dirName
     * @param string $storage 物理存储组，不传则为默认
     * @return boolean
     */
    public static function remove_dir($dir , $storage = 'default')
    {
        $info = File::check_and_get_path($dir);

        if (File::can_do_run($storage))
        {
            if (!is_dir($dir))
            {
                return true;
            }

            $real_path = realpath($dir);

            if (!$real_path || in_array($real_path.DS, File::$sys_dir))
            {
                return true;
            }

            $handle = opendir($dir);
            while (($file = readdir($handle)) !== false)
            {
                if ($file !== '.' && $file !== '..')
                {
                    $tmp_dir = $dir . DS . $file;
                    is_dir($tmp_dir) ? File::remove_dir($tmp_dir) : @unlink($tmp_dir);
                }
            }

            closedir($handle);

            return @rmdir($dir);
        }
        else
        {
            return File::call_http_host($storage, 'file/remove_dir', $info[0], $info[1]);
        }
    }

    /**
     * 转移目录下的所有目录和文件，多服务器可以自动同步，可操作非空目录
     *
     * @param string $from_dir  源文文件目录
     * @param string $to_dir  目标文件目录
     * @param boolean $auto_coverage_old 是否覆盖已有文件，true覆盖，false跳过
     * @param string $storage 物理存储组，不传则为默认
     * @return array($dook,$doerror)
     */
    public static function move_dir($from_dir, $to_dir, $auto_coverage_old = true, $storage = 'default')
    {
        $from_dir = rtrim($from_dir,'\\/').DS;
        $to_dir   = rtrim($to_dir,'\\/')  .DS;

        if ( $from_dir==$to_dir ) return array(0,0);

        $info1 = File::check_and_get_path($from_dir);
        $info2 = File::check_and_get_path($to_dir);

        if (File::can_do_run($storage))
        {
            if (!is_dir($from_dir)) return array(0,0);

            # 完成数
            $do_num = array(0, 0);

            if (!is_dir($to_dir))
            {
                # 创建目标目录
                File::create_dir($to_dir, false, $storage);
            }

            # 列出目录中当前级别的目录和文件
            $files = glob($from_dir .'*');

            foreach ($files as $file)
            {
                # 目标文件
                $to_file = $to_dir . basename($file);

                if (is_dir($file))
                {
                    # 如果当前是目录，则移动目录

                    # 移动目录
                    $donum2 = File::move_dir($file, $to_file, $auto_coverage_old, $storage);
                    if ($donum2)
                    {
                        $do_num[0] += $donum2[0];
                        $do_num[1] += $donum2[1];
                    }
                }
                else
                {
                    # 文件
                    if ($auto_coverage_old && file_exists($to_file))
                    {
                        //覆盖已有文件
                        @unlink($to_file);
                    }

                    if (@rename($file, $to_file))
                    {
                        $do_num[0]++;
                    }
                    else
                    {
                        $do_num[1]++;
                    }
                }
            }

            //移除旧目录
            File::remove_dir($from_dir);

            return $do_num;
        }
        else
        {
            return File::call_http_host($storage, 'file/move_dir', $info1[0], $info1[1], $info2[0], $info2[1], $auto_coverage_old);
        }
    }


    /**
     * 复制目录下的所有目录和文件到另外一个目录
     *
     * @param string $from_dir  源文文件目录
     * @param string $to_dir  目标文件目录
     * @param boolean $auto_coverage_old 是否覆盖已有文件，true覆盖，false跳过
     * @param string $storage 物理存储组，不传则为默认
     * @return array($dook,$doerror)
     */
    public static function copy_dir($from_dir, $to_dir, $auto_coverage_old = true, $storage = 'default')
    {
        $from_dir = rtrim($from_dir, '\\/') . DS;
        $to_dir   = rtrim($to_dir, '\\/') . DS;

        if ($from_dir === $to_dir)return array(0, 0);

        $info1 = File::check_and_get_path($from_dir);
        $info2 = File::check_and_get_path($to_dir);

        if (File::can_do_run($storage))
        {
            if (!is_dir($from_dir)) return array(0, 0);

            # 完成数
            $donum = array(0, 0);

            if (!is_dir($to_dir))
            {
                # 创建目标目录
                File::create_dir($to_dir, false, $storage);
            }

            # 列出目录中当前级别的目录和文件
            $files = glob($from_dir .'*');

            foreach ($files as $file)
            {
                # 目标文件
                $to_file = $to_dir . basename($file);

                if (is_dir($file))
                {
                    # 如果当前是目录，则移动目录

                    # 移动目录
                    $donum2 = File::copy_dir($file, $to_file, $auto_coverage_old, $storage);
                    if ($donum2)
                    {
                        $donum[0] += $donum2[0];
                        $donum[1] += $donum2[1];
                    }
                }
                else
                {
                    # 文件
                    if ($auto_coverage_old && file_exists($to_file))
                    {
                        //覆盖已有文件
                        @unlink($to_file);
                    }

                    if (@copy($file, $to_file))
                    {
                        $donum[0]++;
                    }
                    else
                    {
                        $donum[1]++;
                    }
                }
            }

            return $donum;
        }
        else
        {
            return File::call_http_host($storage,'file/copy_dir', $info1[0], $info1[1], $info2[0], $info2[1], $auto_coverage_old);
        }
    }

    /**
     * 将指定的文件同步到其它服务器
     *
     * @param string $filename
     * @return boolean
     */
    public static function sync($file, $storage = 'default')
    {
        $info = File::check_and_get_path($file);

        if (File::can_do_run($storage))
        {
            # 本机无需操作
            return true;
        }
        else
        {
            if (is_file($file))
            {
                # 通过create_file把文件再创建一次
                return File::create_file($file, file_get_contents($file), null, null, $storage);
            }
            else
            {
                return false;
            }
        }
    }

    /**
     * Split a file into pieces matching a specific size. Used when you need to
     * split large files into smaller pieces for easy transmission.
     *
     *     $count = File::split($file);
     *
     * @param string   file to be split
     * @param string   directory to output to, defaults to the same directory as the file
     * @param integer  $piece_size in MB, for each piece to be
     * @param string $storage 物理存储组，不传则为默认
     * @return integer  The number of pieces that were created
     */
    public static function split($filename, $piece_size = 10 , $storage = 'default')
    {
        $info = File::check_and_get_path($filename);

        if (File::can_do_run($storage))
        {
            // Open the input file
            $file = fopen($filename, 'rb');

            // Change the piece size to bytes
            $piece_size = floor($piece_size * 1024 * 1024);

            // Write files in 8k blocks
            $block_size = 1024 * 8;

            // Total number of peices
            $peices = 0;

            while (!feof($file))
            {
                // Create another piece
                $peices += 1;

                // Create a new file piece
                $piece = str_pad($peices, 3, '0', STR_PAD_LEFT);
                $piece = fopen($filename .'.'. $piece, 'wb+');

                // Number of bytes read
                $read = 0;

                do
                {
                    // Transfer the data in blocks
                    fwrite($piece, fread($file, $block_size));

                    // Another block has been read
                    $read += $block_size;
                }
                while ($read < $piece_size);

                // Close the piece
                fclose($piece);
            }

            // Close the file
            fclose($file);

            return $peices;
        }
        else
        {
            return File::call_http_host($storage, 'file/split', $info[0], $info[1], $piece_size);
        }
    }

    /**
     * Join a split file into a whole file. Does the reverse of [File::split].
     *
     *     $count = File::join($file);
     *
     * @param string $filename  output filename, if different then an the filename
     * @param string $storage 物理存储组，不传则为默认
     * @return integer  The number of pieces that were joined.
     */
    public static function join($filename , $storage = 'default')
    {
        $info = File::check_and_get_path($filename);

        if (File::can_do_run($storage))
        {
            // Open the file
            $file = fopen($filename, 'wb+');

            // Read files in 8k blocks
            $block_size = 1024 * 8;

            // Total number of peices
            $pieces = 0;

            while (is_file($piece = $filename.'.'.str_pad($pieces + 1, 3, '0', STR_PAD_LEFT)))
            {
                // Read another piece
                $pieces += 1;

                // Open the piece for reading
                $piece = fopen($piece, 'rb');

                while (!feof($piece))
                {
                    // Transfer the data in blocks
                    fwrite($file, fread($piece, $block_size));
                }

                // Close the peice
                fclose($piece);
            }

            return $pieces;
        }
        else
        {
            return File::call_http_host($storage, 'file/join', $info[0], $info[1]);
        }
    }

    /**
     * 返回指定文件类型
     *
     *     $mime = File::mime($file);
     *
     * @param   string  file name or path
     * @return  string  mime type on success
     * @return  FALSE   on failure
     */
    public static function mime($filename)
    {
        // Get the complete path to the file
        $filename = realpath($filename);

        // Get the extension from the filename
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

        if (preg_match('/^(?:jpe?g|png|[gt]if|bmp|swf)$/', $extension) && function_exists('getimagesize'))
        {
            // Use getimagesize() to find the mime type on images
            $file = getimagesize($filename);

            if (isset($file['mime']))
            {
                return $file['mime'];
            }
        }

        if (class_exists('finfo', false))
        {
            $info = new finfo(defined('FILEINFO_MIME_TYPE') ? FILEINFO_MIME_TYPE : FILEINFO_MIME);
            if ($info)
            {
                return $info->file($filename);
            }
        }

        if (function_exists('mime_content_type'))
        {
            return mime_content_type($filename);
        }

        if (!empty($extension))
        {
            return File::mime_by_ext($extension);
        }

        return false;
    }

    /**
     * Return the mime type of an extension.
     *
     *     $mime = File::mime_by_ext('png'); // "image/png"
     *
     * @param   string  $extension extension: php, pdf, txt, etc
     * @return  string  mime type on success
     * @return  FALSE   on failure
     */
    public static function mime_by_ext($extension)
    {
        $mimes = Core::config('mimes');

        return isset($mimes[$extension])?$mimes[$extension][0]:false;
    }

    /**
     * Lookup MIME types for a file
     *
     * @see Kohana_File::mime_by_ext()
     * @param string $extension Extension to lookup
     * @return array Array of MIMEs associated with the specified extension
     */
    public static function mimes_by_ext($extension)
    {
        $mimes = Core::config('mimes');

        return isset($mimes[$extension])?((array)$mimes[$extension]):array();
    }

    /**
     * Lookup file extensions by MIME type
     *
     * @param   string  $type File MIME type
     * @return  array   File extensions matching MIME type
     */
    public static function exts_by_mime($type)
    {
        static $types = array();

        // Fill the static array
        if (empty($types))
        {
            $mimes = Core::config('mimes');
            foreach ($mimes as $ext => $ms)
            {
                foreach ($ms as $mime)
                {
                    if ($mime == 'application/octet-stream')
                    {
                        // octet-stream is a generic binary
                        continue;
                    }

                    if (!isset($types[$mime]))
                    {
                        $types[$mime] = array((string)$ext);
                    }
                    elseif (!in_array($ext, $types[$mime]))
                    {
                        $types[$mime][] = (string)$ext;
                    }
                }
            }
        }

        return isset($types[$type])?$types[$type]:false;
    }

    /**
     * Lookup a single file extension by MIME type.
     *
     * @param   string  $type  MIME type to lookup
     * @return  mixed          First file extension matching or false
     */
    public static function ext_by_mime($type)
    {
        return current(File::exts_by_mime($type));
    }

    /**
     * 调用HttpServer执行
     *
     * @param string $storage
     * @param string $uri
     * @param mixed $arg1
     * @param mixed $arg2
     * @return boolean mixed
     */
    protected static function call_http_host($storage, $uri, $arg1 = null, $arg2 = null)
    {
        $param_arr = func_get_args();
        array_shift($param_arr); // 把 $storage 移除

        $sync_mode = File::sync_mode();

        if ($sync_mode == 'rsync')
        {
            // rsync 模式，调用主服执行
            $action = 'master_exec';
        }
        else
        {
            // 全部同步执行
            $action = 'sync_exec';
        }

        $rs = call_user_func_array(array(HttpCall::factory($storage), $action), $param_arr);

        if (IS_DEBUG)Core::debug()->log($rs);

        if (is_array($rs))
        {
            $i = 0;
            foreach ($rs as $item)
            {
                $i++;

                if ($item!=='success')
                {
                    if (IS_DEBUG)
                    {
                        Core::debug()->error($i .'/'. count($rs), 'call_http_host rs status');
                    }
                    return false;
                }
            }
        }
        else
        {
            if ($rs==='success')
            {
                return true;
            }
            else
            {
                Core::debug()->error('call_http_host error.');
                return false;
            }
        }

        return $rs;
    }

    /**
     * 获取同步模式
     *
     * @return string
     */
    protected static function sync_mode()
    {
        $mode = Core::config('file_sync_mode');
        if (null===$mode)$mode = 'default';

        return $mode;
    }

    /**
     * 是否可以直接执行
     *
     * @param string $storage
     * @return boolean
     */
    protected static function can_do_run($storage)
    {
        return (IS_SYSTEM_MODE || 'none' == File::sync_mode() || count(Core::config('web_server_list.' . $storage)) <= 1) ? true : false;
    }

    /**
     * 检查并且获取系统允许读写路径信息，如果不合法则返回false
     *
     * @param string/array $file
     * @return array($key,$path)
     * @throws Exception
     */
    protected static function check_and_get_path($file)
    {
        if (is_array($file))
        {
            $array_mode = true;
        }
        else
        {
            $array_mode = false;
            $file = (array)$file;
        }

        $data = array();
        foreach (File::$dir as $key=>$path)
        {
            $len = strlen($path);
            foreach ($file as $f)
            {
                $s = substr($f,0,$len);
                if ($s == $path)
                {
                    if (!$array_mode)
                    {
                        # 单文件模式，直接返回
                        return array($key,substr($f,$len));
                    }
                    else
                    {
                        # 多文件，加入列表
                        $data[0][] = $key;
                        $data[1][] = substr($f,$len);
                    }
                }
            }
        }

        if ($array_mode && $data)return $data;

        throw new Exception(__('Not allowed to operate the corresponding directory'));
    }


    /**
     * 初始化文件对象
     *
     * @param string $file 文件名
     */
    public function __construct($file)
    {

    }
}