/**
  * Prepare
  * Call from elFinder::netmout() before volume->mount().
  *
  * @return array
  *
  * @author Naoki Sawada
  * @author Raja Sharma updating for GoogleDrive
  **/
 public function netmountPrepare($options)
 {
     if (empty($options['client_id']) && defined('ELFINDER_GOOGLEDRIVE_CLIENTID')) {
         $options['client_id'] = ELFINDER_GOOGLEDRIVE_CLIENTID;
     }
     if (empty($options['client_secret']) && defined('ELFINDER_GOOGLEDRIVE_CLIENTSECRET')) {
         $options['client_secret'] = ELFINDER_GOOGLEDRIVE_CLIENTSECRET;
     }
     if (empty($options['googleApiClient']) && defined('ELFINDER_GOOGLEDRIVE_GOOGLEAPICLIENT')) {
         $options['googleApiClient'] = ELFINDER_GOOGLEDRIVE_GOOGLEAPICLIENT;
         include_once $options['googleApiClient'];
     }
     if (!isset($options['pass'])) {
         $options['pass'] = '';
     }
     try {
         $client = new \Google_Client();
         $client->setClientId($options['client_id']);
         $client->setClientSecret($options['client_secret']);
         if ($options['pass'] === 'reauth') {
             $options['pass'] = '';
             $this->session->set('GoogleDriveAuthParams', [])->set('GoogleDriveTokens', []);
         } elseif ($options['pass'] === 'googledrive') {
             $options['pass'] = '';
         }
         $options = array_merge($this->session->get('GoogleDriveAuthParams', []), $options);
         if (!isset($options['access_token'])) {
             $options['access_token'] = $this->session->get('GoogleDriveTokens', []);
             $this->session->remove('GoogleDriveTokens');
         }
         $aToken = $options['access_token'];
         $rootObj = $service = null;
         if ($aToken) {
             try {
                 $client->setAccessToken($aToken);
                 if ($client->isAccessTokenExpired()) {
                     $aToken = array_merge($aToken, $client->fetchAccessTokenWithRefreshToken());
                     $client->setAccessToken($aToken);
                 }
                 $service = new \Google_Service_Drive($client);
                 $rootObj = $service->files->get('root');
                 $options['access_token'] = $aToken;
                 $this->session->set('GoogleDriveAuthParams', $options);
             } catch (Exception $e) {
                 $aToken = [];
                 $options['access_token'] = [];
                 if ($options['user'] !== 'init') {
                     $this->session->set('GoogleDriveAuthParams', $options);
                     return ['exit' => true, 'error' => elFinder::ERROR_REAUTH_REQUIRE];
                 }
             }
         }
         if ($options['user'] === 'init') {
             if (empty($options['url'])) {
                 $options['url'] = elFinder::getConnectorUrl();
             }
             $callback = $options['url'] . '?cmd=netmount&protocol=googledrive&host=1';
             $client->setRedirectUri($callback);
             if (!$aToken && empty($_GET['code'])) {
                 $client->setScopes([Google_Service_Drive::DRIVE]);
                 if (!empty($options['offline'])) {
                     $client->setApprovalPrompt('force');
                     $client->setAccessType('offline');
                 }
                 $url = $client->createAuthUrl();
                 $html = '<input id="elf-volumedriver-googledrive-host-btn" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" value="{msg:btnApprove}" type="button" onclick="window.open(\'' . $url . '\')">';
                 $html .= '<script>
                     $("#' . $options['id'] . '").elfinder("instance").trigger("netmount", {protocol: "googledrive", mode: "makebtn"});
                 </script>';
                 if (empty($options['pass']) && $options['host'] !== '1') {
                     $options['pass'] = '******';
                     $this->session->set('GoogleDriveAuthParams', $options);
                     return ['exit' => true, 'body' => $html];
                 } else {
                     $out = ['node' => $options['id'], 'json' => '{"protocol": "googledrive", "mode": "makebtn", "body" : "' . str_replace($html, '"', '\\"') . '", "error" : "' . elFinder::ERROR_ACCESS_DENIED . '"}', 'bind' => 'netmount'];
                     return ['exit' => 'callback', 'out' => $out];
                 }
             } else {
                 if (!empty($_GET['code'])) {
                     $aToken = $client->fetchAccessTokenWithAuthCode($_GET['code']);
                     $options['access_token'] = $aToken;
                     $this->session->set('GoogleDriveTokens', $aToken)->set('GoogleDriveAuthParams', $options);
                     $out = ['node' => $options['id'], 'json' => '{"protocol": "googledrive", "mode": "done", "reset": 1}', 'bind' => 'netmount'];
                     return ['exit' => 'callback', 'out' => $out];
                 }
                 $folders = [];
                 foreach ($service->files->listFiles(['pageSize' => 1000, 'q' => 'trashed = false and mimeType = "application/vnd.google-apps.folder"']) as $f) {
                     $folders[$f->getId()] = $f->getName();
                 }
                 natcasesort($folders);
                 $folders = ['root' => $rootObj->getName()] + $folders;
                 $folders = json_encode($folders);
                 $expires = empty($aToken['refresh_token']) ? $aToken['created'] + $aToken['expires_in'] - 30 : 0;
                 $json = '{"protocol": "googledrive", "mode": "done", "folders": ' . $folders . ', "expires": ' . $expires . '}';
                 $options['pass'] = '******';
                 $html = 'Google.com';
                 $html .= '<script>
                     $("#' . $options['id'] . '").elfinder("instance").trigger("netmount", ' . $json . ');
                 </script>';
                 $this->session->set('GoogleDriveAuthParams', $options);
                 return ['exit' => true, 'body' => $html];
             }
         }
     } catch (Exception $e) {
         $this->session->remove('GoogleDriveAuthParams')->remove('GoogleDriveTokens');
         if (empty($options['pass'])) {
             return ['exit' => true, 'body' => '{msg:' . elFinder::ERROR_ACCESS_DENIED . '}' . ' ' . $e->getMessage()];
         } else {
             return ['exit' => true, 'error' => [elFinder::ERROR_ACCESS_DENIED, $e->getMessage()]];
         }
     }
     if (!$aToken) {
         return ['exit' => true, 'error' => elFinder::ERROR_REAUTH_REQUIRE];
     }
     if ($options['path'] === '/') {
         $options['path'] = 'root';
     }
     try {
         $file = $service->files->get($options['path']);
         $options['alias'] = sprintf($this->options['gdAlias'], $file->getName());
     } catch (Google_Service_Exception $e) {
         $err = json_decode($e->getMessage(), true);
         if (isset($err['error']) && $err['error']['code'] == 404) {
             return ['exit' => true, 'error' => [elFinder::ERROR_TRGDIR_NOT_FOUND, $options['path']]];
         } else {
             return ['exit' => true, 'error' => $e->getMessage()];
         }
     } catch (Exception $e) {
         return ['exit' => true, 'error' => $e->getMessage()];
     }
     foreach (['host', 'user', 'pass', 'id', 'offline'] as $key) {
         unset($options[$key]);
     }
     return $options;
 }
 /**
  * @param string $refreshToken
  * @return array
  */
 public function getAccessToken($refreshToken)
 {
     $this->googleApiClient->fetchAccessTokenWithRefreshToken($refreshToken);
     return $this->googleApiClient->getAccessToken();
 }
 /**
  * "Mount" volume.
  * Return true if volume available for read or write,
  * false - otherwise
  *
  * @param array $opts
  * @return bool
  * @author Naoki Sawada
  */
 public function mount(array $opts)
 {
     $creds = null;
     if (isset($opts['access_token'])) {
         $this->netMountKey = md5(join('-', array('googledrive', $opts['path'], isset($opts['access_token']['refresh_token']) ? $opts['access_token']['refresh_token'] : $opts['access_token']['access_token'])));
     }
     $client = new \Google_Client();
     $client->setClientId($opts['client_id']);
     $client->setClientSecret($opts['client_secret']);
     if (!empty($opts['access_token'])) {
         $client->setAccessToken($opts['access_token']);
     }
     if ($client->isAccessTokenExpired()) {
         try {
             $creds = $client->fetchAccessTokenWithRefreshToken();
         } catch (LogicException $e) {
             $this->session->remove('GoogleDriveAuthParams');
             throw $e;
         }
     }
     $service = new \Google_Service_Drive($client);
     // If path is not set, use the root
     if (!isset($opts['path']) || $opts['path'] === '') {
         $opts['path'] = 'root';
     }
     $googleDrive = new GoogleDriveAdapter($service, $opts['path'], ['useHasDir' => true]);
     $opts['fscache'] = null;
     if ($this->options['gdCacheDir'] && is_writeable($this->options['gdCacheDir'])) {
         if ($this->options['gdCacheExpire']) {
             $opts['fscache'] = new elFinderVolumeFlysystemGoogleDriveCache(new Local($this->options['gdCacheDir']), $this->options['gdCachePrefix'] . $this->netMountKey, $this->options['gdCacheExpire']);
         }
     }
     if ($opts['fscache']) {
         $filesystem = new Filesystem(new CachedAdapter($googleDrive, $opts['fscache']));
     } else {
         $filesystem = new Filesystem($googleDrive);
     }
     $opts['driver'] = 'FlysystemExt';
     $opts['filesystem'] = $filesystem;
     $opts['checkSubfolders'] = true;
     if (!isset($opts['alias'])) {
         $opts['alias'] = 'GoogleDrive';
     }
     if ($res = parent::mount($opts)) {
         // update access_token of session data
         if ($creds) {
             $netVolumes = $this->session->get('netvolume');
             $netVolumes[$this->netMountKey]['access_token'] = array_merge($netVolumes[$this->netMountKey]['access_token'], $creds);
             $this->session->set('netvolume', $netVolumes);
         }
     }
     return $res;
 }