/**
  * Syncronize with remote server if $opt[remote_server] (self::$sync[server]) is set.
  * Local path is $opt[cwd]|getcwd().'/'.$this->path.
  * Remote path is $opt[remote_server]|self::$sync[server].'/'.$opt[remote_path]|self::$sync[cwd].'/'.$opt[remote_file]|$this->path
  * Auto create local file directory if necessary. Retrieve remote md5 with remote_server.'/'.self::$sync[bin]?path=remote_file. 
  * If $this->json_format is set save file information to local_file.json. Use md5_old to avoid unnecessary downloads.
  *
  * @param map $opt see constructor
  * 
  */
 public function synchronize($opt)
 {
     if (is_null($this->path) || mb_strlen($this->path) == 0) {
         throw new Exception('empty path');
     }
     if (empty(self::$sync['server']) && empty($opt['remote_server'])) {
         return;
     }
     try {
         $cwd = empty($opt['cwd']) ? getcwd() : $opt['cwd'];
         $rserver = empty($opt['remote_server']) ? self::$sync['server'] : $opt['remote_server'];
         $rfile = empty($opt['remote_file']) ? $this->path : $opt['remote_file'];
         $rpath = '';
         if (isset($opt['remote_path'])) {
             $rpath = $opt['remote_path'];
         } else {
             if (!empty(self::$sync['cwd'])) {
                 $rpath = self::$sync['cwd'];
             }
         }
         $this->remote_path = empty($rpath) ? $rfile : $rpath . '/' . $rfile;
         $dir = $cwd . '/' . dirname($this->path);
         if (!Dir::exists($dir)) {
             Dir::create($dir, 0, true);
         }
         $this->path_absolute = empty(realpath($dir)) ? $dir . '/' . $this->path : realpath($dir) . '/' . basename($this->path);
         $file_exists = File::exists($this->path_absolute);
         if (empty(self::$sync['bin']) && ($file_exists || !empty($this->json_format))) {
             throw new Exception('missing sync.bin', print_r(self::$sync, true));
         } else {
             $server_bin = $rserver . '/' . self::$sync['bin'];
         }
         if (!empty($this->json_format)) {
             $json_file = $this->path_absolute . '.json';
             if (File::exists($json_file)) {
                 $this->scanJSON();
             }
             $json_query = $server_bin . '?format=' . $this->json_format . '&path=' . urlencode($this->remote_path);
             try {
                 $remote_json_str = File::fromURL($json_query);
                 $remote_json = JSON::decode($remote_json_str);
             } catch (Exception $e) {
                 throw new Exception('failed to retrive file information', "query={$json_query} result={$remote_json_str}");
             }
             if ($this->md5 != $remote_json['md5']) {
                 File::save($json_file, $remote_json_str);
                 $this->scanJSON($remote_json);
                 $this->is_modified = 1;
             } else {
                 $this->is_modified = 0;
             }
         } else {
             if (!$file_exists) {
                 File::save($this->path_absolute, File::fromURL($rserver . '/' . $this->remote_path));
                 $this->is_modified = 1;
                 $this->scanFile();
             } else {
                 $this->scanFile();
                 $remote_json = JSON::decode(File::fromURL($server_bin . '?path=' . urlencode($this->remote_path)));
                 if (empty($remote_json['md5'])) {
                     throw new Exception('remote md5 missing', print_r($remote_json, true));
                 }
                 if ($remote_json['md5'] != $this->md5) {
                     File::save($this->path_absolute, File::fromURL($rserver . '/' . $this->remote_path));
                     $this->is_modified = 1;
                     $this->scanFile();
                 } else {
                     $this->is_modified = 0;
                 }
             }
         }
         $this->is_synchronized = 1;
     } catch (Exception $e) {
         if (!empty(self::$sync['abort'])) {
             throw $e;
         } else {
             $this->is_synchronized = 0;
         }
     }
 }