php/googledriveOld.php

<?php

/* Google Drive Interface
    docs + links
        api:            https://developers.google.com/drive/api/guides/about-sdk
        file fields:    https://developers.google.com/drive/api/reference/rest/v3/files
        guide           https://developers.google.com/drive/api/guides/manage-uploads  examples in php http etc.
    ----------- installation
    download latest release, select zip with appropriate release and expand
        https://github.com/googleapis/google-api-php-client/releases
            expand and remove in vendor/google/apiclient-services/src everything except Drive and Drive.php
        https://github.com/googleapis/google-api-php-client-services/releases
*/
require_once 'net.php';
require_once 'googleClientLib/google-api-small/vendor/autoload.php';

GoogleDrive::ini();
class GoogleDrive {
    /*
        docs: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/index.html 
    */
    public const TYPEMAP = ['application/vnd.google-apps.folder' => CLOUD::DIR];

    public static $cfg = [
              # 0=>google field, 1=>row key,2=>fmtMethBegin, 3=>fmtMethEnd  
            [ ['nextPageToken', '', 'aPpl', 0, 0]
            , ['name', 'name', 'aText']
            , ['mimeType', 'type','aType']
            , ['id', 'id', 'aText']
            , ['modifiedTime', 'modTst', 'aTst']
            , ['size', 'size', 'aText']
            , ['owners', 'owner', 'ownerB', '', 'owners(emailAddress)']
            # , ['d:href', 'path', '', 'hrefE', '']
            , ['parents', 'parent', 'parentB'] 
            , ['createdTime', 'creTst', '   aTst  ']  
            ], ['path' => 1, 'creTst' => 1]];

    public static function ini() {
        self::$cfg = Pipeline::cfgBase(...self::$cfg);
        foreach(Cloud::ROWKYS as $nm => $flds)
            self::cfgAdd($nm, $flds);
    }

    public static function cfgAdd($nm, $flds) {
        $c = Pipeline::cfg(self::$cfg, $flds);
        $c['gdFlds'] = implode(',', $c['flds']);
        self::$cfg[$nm] = isset(self::$cfg[$nm]) ? err("googledrive cfg key $nm already defined") : $c;
    }

    public function pipeline($cfg=null, $trg = null) {
        if (! is_array($cfg)) {
            if (! $trg) 
                list($cfg, $trg) = explode(',', "$cfg,");           
            $cfg or $cfg = 'file';
            $cfg = self::$cfg[$cfg] ?? err("cfg $cfg not in", array_keys(self::$cfg));
        }
        $trg or $trg = 'metaRowOut';
        $p = new Pipeline($this, $cfg);
        $p->req = new Net2Str;
        $p->httpChk = 'HTTP/1.1 200 OK';
        if ($trg === 'string')
            return $p;
        $p->json = $trg !== 'jsonObj';
        if (str_starts_with($trg, 'json'))
            return $p;
        $p->walk = new WalkJson($p); #, allB: 'outB', allE: 'outE');
        $p->walk->typeMap = self::TYPEMAP;
        return $p->makeWalk($trg);
    }

    public $cloudId, $user, $meta;
    private $srv, $fiFi = 'name, id, mimeType, modifiedTime, owners(emailAddress), size, parents';
    public function __construct($gc, $id, $ty, $cliN, $us) {
        $this->gc = $gc;
        $this->srv = new Google\Service\Drive($gc);
        $this->cloudId = $id;
        $this->user = $us;
        $this->accessTk = $gc->getAccessToken();
        $this->hAuth = "Authorization: Bearer " . $this->accessTk['access_token'];
        dbg1("auth $this->hAuth, accessToken", $this->accessTk);
        $r = $this->get('root');
        if ($r[5] !== $this->user)
            err("mismatch GoogleDrive(user=$this->user) but root user $r[5]:", $r);
    }

    function meta($fun, $dir) {
        $now = new DateTime();
        return ['timestamp' => toLocalTst($now) , 'cloudId' => $this->cloudId, 'fun' => $fun, 'dir' => $dir
            , 'user'=> $this->user,  'timeZone' => $now->format('e P')];
    }

    public function getHTTP($ppl, $gid, $gp=false) {
        is_object($ppl) or $ppl = $this->pipeline($ppl);
        $ppl -> meta = $this->meta('getHTTP', '');
         #   "Authorization: Bearer " . $this->accessTk['access_token']ACCESS_TOKEN"
        $ctx = stream_context_create(['http' => ['header' => $this->hAuth
              #. "\nAccept: application/json" # . ' realm="https://www.googleapis.com/auth/documents.readonly"' # . $this->accessTk['scope'] . '"' 
              #  , 'method' => 'PUT'
            ]]);
        return $ppl->request("https://www.googleapis.com/drive/v3/files/$gid?fields=" . rawurlencode($ppl->cfg['gdFlds']), $ctx);
        if(is_null($cnt) or $http_response_header[0] !== 'HTTP/1.1 200 OK') {
            if ($http_response_header[0] === 'HTTP/1.1 404 Not Found')
                return null;
            err(__METHOD__ . "get($gid)", error_get_last(), ", response", $http_response_header);
        };
        return json_decode($cnt);
         $g = $f = $this->srv->files->get($gid, ['fields' => $this->fiFi]);
        if (! ($id = $g->getParents()[0] ?? false)) {
            $p = './';
        } elseif ($gp) {
            $p = $g->getName() . substr('/', 0, 'application/vnd.google-apps.folder' === $f->getMimeType());
            while(true) {
                $g = $this->srv->files->get($id, ['fields' => 'name, parents']);
                if ( ! ($id = $g->getParents()[0] ?? false))
                    break;
                $p = $g->getName() . "/$p";
            } 
        }
       return [$f->getName(), $f->getId(), $f->getMimeType(), $p ?? ''
                       , toLocalTst($f->getModifiedTime()), $f->getOwners()[0]->getEmailAddress(), $f->getSize()
                       , $f->getParents()[0] ?? null, 0];
    }

    public function ffHTTP($ppl, $dirId='root', $whr=false) {
        is_object($ppl) or $ppl = $this->pipeline($ppl);
        # $dirNd = $this->get($dirId, true); 
        $ppl->meta = $this->meta('ffHTTP', '???');    
        $ppl->request(0, 0, 1);

        $ctx = stream_context_create(['http' => ['header' => $this->hAuth]]);
        $qp  = 'pageSize=5'
             . "&fields=" . rawurlencode('nextPageToken,files(' . $ppl->cfg['gdFlds']) . ')'
             . "&q=" . rawurlencode("\"$dirId\" in parents and not trashed" . ($whr ? " and ($whr)" :  ''))
             . '&orderBy=name';
        $ppl->nextPageToken = false;
        $r = $ppl->request("https://www.googleapis.com/drive/v3/files?$qp", $ctx, 2);
        while ($pt = $ppl->nextPageToken) {
            $ppl->nextPageToken = false;
            $r = $ppl->request("https://www.googleapis.com/drive/v3/files?$qp&pageToken=$pt", $ctx, 2);
        }
        $ppl->request(0, 0, 4);        
        return $r;
        $this->ffFi = $this->ffFo = $this->ffSz = 0;
        $dirNd[3] = '';
        ($this->ffHn)($dirNd);
        $this->ff1(1, $dirId, '');
    }


    public function get($gid, $gp=false) {
        $g = $f = $this->srv->files->get($gid, ['fields' => $this->fiFi]);
        if (! ($id = $g->getParents()[0] ?? false)) {
            $p = './';
        } elseif ($gp) {
            $p = $g->getName() . substr('/', 0, 'application/vnd.google-apps.folder' === $f->getMimeType());
            while(true) {
                $g = $this->srv->files->get($id, ['fields' => 'name, parents']);
                if ( ! ($id = $g->getParents()[0] ?? false))
                    break;
                $p = $g->getName() . "/$p";
            } 
        }
       dbg1("drive client token", $this->gc->getAccessToken());
       return [$f->getName(), $f->getiD(), $f->getMimeType(), $p ?? ''
                       , toLocalTst($f->getModifiedTime()), $f->getOwners()[0]->getEmailAddress(), $f->getSize()
                       , $f->getParents()[0] ?? null, 0];
    }

     public function ffSim($q, $hn=null) {
       $opt =   [ 'pageSize' => 100
                , 'fields' => "nextPageToken, files($this->fiFi)"
                , 'q' => 'not trashed'. ($q ? " and ($q)" :  '')
                , 'orderBy' => 'name'
                ];
        $this->ffFi = $this->ffFo = $this->ffSz = 0;
        if ($hn) 
            return ffOpt($opt, $hn);
        $res = [];
        $this->ffOpt($opt, function ($n) use(&$res) {$res[] = $n; });
        return $res;
        }

        
     public function ffOpt($opt, $hn, $pPa = '') {
        out("ffOpt", $opt);
        do {
            $results = $this->srv->files->listFiles($opt);
            foreach ($files=$results->getFiles() as $f) {
                $fo = 'application/vnd.google-apps.folder' === $ty = $f->getMimeType();
                if ($fo) {
                    $this->ffFo++;
                } else {
                    $this->ffFi++;
                    $this->ffSz += $f->getSize();
                }
                $p = "$pPa{$f->getName()}" . substr('/', 0, $fo);
                $hn([$f->getName(), $f->getiD(), $f->getMimeType(), $p
                       , toLocalTst($f->getModifiedTime()), $f->getOwners()[0]->getEmailAddress(), $f->getSize()
                       ,  $f->getParents()[0]]);
            }
        } while ($opt['pageToken'] = $results->getNextPageToken());
    }

    public function ff($dirId, $hn, $hMeta=null, $whr=null) {
        $dirId or $dirId='root';
        $dirNd = $this->get($dirId, true); 
        $this->meta = $this->meta('ff', $dirNd[3]);    
        if ($hMeta)
            $hMeta($this->meta);
        $ix = 0;
        $this->ffHn = $hn;
        $this->ffOpt = [ 'pageSize' => 100
                , 'fields' => "nextPageToken, files($this->fiFi)"
                , 'q' => ' in parents and not trashed' . ($whr ? " and ($whr)" :  '')
                , 'orderBy' => 'name'
                ];
        $this->ffFi = $this->ffFo = $this->ffSz = 0;
        $dirNd[3] = '';
        ($this->ffHn)($dirNd);
        $this->ff1(1, $dirId, '');
    }

    function ff1($lv, $pId, $pPa) {
        $opt = $this->ffOpt; 
        $opt['q'] = "\"$pId\" $opt[q]";
        do {
            $results = $this->srv->files->listFiles($opt);
            foreach ($files=$results->getFiles() as $f) {
                $fo = 'application/vnd.google-apps.folder' === $ty = $f->getMimeType();
                if ($fo) {
                    $this->ffFo++;
                } else {
                    $this->ffFi++;
                    $this->ffSz += $f->getSize();
                }
                $p = "$pPa{$f->getName()}" . substr('/', 0, $fo);
                ($this->ffHn)([$f->getName(), $f->getiD(), $ty, $p
                       , toLocalTst($f->getModifiedTime()), $f->getOwners()[0]->getEmailAddress(), $f->getSize()
                       ,  $f->getParents()[0], $lv]);
                if ($fo)
                    $this->ff1($lv+1, $f->getiD(), $p);
            }
        } while ($opt['pageToken'] = $results->getNextPageToken());
    }

    public function create($pa, $fn, $ty, $cnt) {
         $fileMetadata = new Google\Service\Drive\DriveFile(['name' => $fn, 'parents' => [$pa]]);
         $file = $this->srv->files->create($fileMetadata
          , [ 'data' => $cnt . "\n" . toLocalTst('now') . "\n und so weiter"
            , 'mimeType' => $ty
            , 'uploadType' => 'multipart'
            , 'fields' => 'id'
            , 'keepRevisionForever' => true
           ]);
        out("File ID: $file->id");
        return $file->id;
    }

    public function uploadversion($gid, $ty, $cnt) {
        $fileMetadata = new Google\Service\Drive\DriveFile();        
        $f = $this->srv->files->update($gid, $fileMetadata
          , [ 'data' => $cnt . "]]," . toLocalTst('now') . ",upload time"
         #   , 'mimeType' => $ty
            , 'uploadType' => 'multipart'
            , 'fields' => $this->fiFi
            , 'keepRevisionForever' => true
            ]);
        out('uploadVers file ID:', [$f->getName(), $f->getiD(), $f->getMimeType(), $p ?? ''
                       , toLocalTst($f->getModifiedTime()), $f->getOwners()[0]->getEmailAddress(), $f->getSize()
                       , $f->getParents()[0] ?? null]);
        return $f->id;
    }


    public function upload($fn, $ty, $cnt) {
        $ff = $this->ffSim("\"root\" in parents and name =\"$fn\"" );
        if (count($ff) === 0)
            $this->create('root', $fn, $ty, $cnt);
        elseif (count($ff) === 1)
            $this->uploadversion($ff[0][1], $ty, $cnt);
        else
            err("upload $fn has " . count($ff) . " nodes", $ff);
    }

    public function mv($fP, $tP) {
        /*  move file/directory $fr to new path/name $to
        */
        $fr =implode('/', array_map('rawurlencode', is_array($fP) ? $fP : explode('/', $fP)));
        $to =implode('/', array_map('rawurlencode', is_array($tP) ? $tP :explode('/', $tP)));
        $ctx = stream_context_create(['http' => [
                'header' => "$this->hAuth\nDestination: $this->fUri$to\nOverwrite: F"
                , 'method' => 'MOVE'
                ]]);
        $r = file_get_contents("$this->fUri$fr", 0, $ctx);
        if ($http_response_header[0] !== 'HTTP/1.1 201 Created')
            err("mv $fr to $to failed: http_response_header", $http_response_header);
    }

    public function putFi($fn, $cnt, $ty='text/csv') {
        // upload to host $hst a file named $fn and contents $cnt, if the file already existst, create new version of the same id
        $ctx = stream_context_create(['http' => [
                'header' => "Content-Type: $ty\n$this->hAuth"
                , 'method' => 'PUT'
                , 'content' => $cnt
                ]]);
        $r = file_get_contents("$this->fUri$fn", 0, $ctx);
        echo "putFi sent " .strlen($cnt) ." chars to $this->cloudId $this->host $fn, response $http_response_header[0]\n";
        if ($http_response_header[0] !== "HTTP/1.1 201 Created" and $http_response_header[0] !== "HTTP/1.1 204 No Content")
            err("putFi $fn failed, response:>n ", $http_response_header);
    }
    public function byId($id, $dir='') {
       $ctx = stream_context_create($c = ['http' => [
                'header' => "Content-Type: text/xml\n$this->hAuth" 
                , 'method' => 'SEARCH'
                , 'content' => <<<data
            <?xml version="1.0" encoding="UTF-8"?>
            <d:searchrequest xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
                <d:basicsearch>
                    <d:select>
                         <d:prop>
                             <oc:fileid/>
                             <d:displayname/>
                             <d:getcontenttype/>
                             <d:getetag/>
                             <oc:size/>
                         </d:prop>
                   </d:select>
                    <d:from>
                        <d:scope>
                            <d:href>/files/$this->rootDir/</d:href>
                            <d:depth>infinity</d:depth>
                        </d:scope>
                    </d:from>
                    <d:where>
                        <d:eq>
                            <d:prop>
                                <oc:fileid/>
                            </d:prop>
                            <d:literal>$id</d:literal>
                        </d:eq>
                    </d:where>
                </d:basicsearch>
            </d:searchrequest> 
            data ]]);
        $r = file_get_contents("$this->sUri", 0, $ctx);
        echo "search $this->sUri response $http_response_header[0]\n";
        var_dump($http_response_header);
        return $r;
    }
    public function rename($r, $f2v, $hi) { 
        /* check all resources in xmmResult $r, if they need to be renamed
            $f2v: callback: rename last level of path
            $hi($nx, $n, $fP, $fM, $tP): callback if last level should be rename:
                $nx=count, $n array for resource, $fP from path old, $fM from path for mv, $tp to path for mv
        */  
        $nx = -1;
        $fP = $tP = [];
        $hR = function ($n) use (&$nx, &$fP, &$tP, $f2v, $hi) {
            $nx++;
            $aS = $n[0];
            if (($fo = $n[2] === 'd:collection') !== str_ends_with($aS, '/'))
                err("path $aS mismatches type $n[2]");
            if ($aS === './')
                return; 
            $aP = explode('/', $fo ? substr($aS, 0, -1) : $aS);
            $aV = $f2v($aL = $aP[$aY = count($aP) - 1]);
            if ($aL === $aV)
                return;
            $y = min($aY, count($fP));
            for ($x=0; $x < $y and $aP[$x] === $fP[$x]; $x++) ; # index of first difference
            out("a) $x $aY fP", $fP, ", tP", $tP);
            for ( ; $x < $aY ; $x++)                            # copy new levels
                $fP[$x] = $tP[$x] = $aP[$x];
            out("b) $x $aY fP", $fP, ", tP", $tP);
            while (array_key_last($fP) > $aY)
                array_pop($fP);
            while (array_key_last($tP) > $aY)
                array_pop($tP);
            $fM = $tP;
            $fP[$aY] = $fM[$aY] = $aL;
            $tP[$aY] = $aV;
            $hi($nx, $n, $fP, $fM, $tP);
        };
        $this->ffRsrc($r, $hR);
        echo "$nx nodes in $this->cloudId\n";
    }
} # end class GoogleDrive