AKTUALIZACE 31.1.2017 - Jak je zmíněno v komentářích, API se mění. Článek jsem aktualizoval pro fungování s APIv3.
Pokud přecházíte z APIv2, prvně si přečtěte článek Přechod z GApi v2 na v3, případně Migration to Google Drive APIv3 na stránkách Google Developers.
V předchozích článcích jsem ukázal, jak lze vytvořit zálohu MySQL pomocí PHP skriptu. Sice si vytvořím zálohu, ale ta je stále uložena na stejném serveru a v případě výpadku jsem přišel pravděpodobně o obojí. Proto jsem se rozhodl napsat skript, který zálohu nahraje na Google Drive.
OAuth2 Service account
Stejně jako v minulém článku při stahování dat z Google Kalendáře, musíme vytvořit servisní účet, abychom mohli přistupovat k účtu bez autorizace. V Google Consoli vytvoříme nový účet, a postupujte podle návodu v minulém článku o Kalendáři.
Nezapomeňte stáhnout privátní klíč ve formátu JSON, v APIv2 to byl formát P12. Na závěr je nutné přidat v levém menu Library povolit u účtu Drive API. Po přidání může trvat až 10 minut, než se API stane aktivní a budete se moci připojit ze skriptu.
Sdílení složky a její ID pro service account
Service account ale nemá nic společného s našim účtem, proto je potřeba soubory a složky nasdílet. Servisní účet je emailová adresa ve tvaru user@ucet-xx.iam.gserviceaccount.com. Na Google Drive si vytvoříme složku a nasdílíme ji s tímto emailem.
V našem skriptu budeme potřebovat znát také ID složky pro nahrávání, kterou můžeme zjistit pomocí API nebo jednodušeji v URL. Stačí ale nasdílet pouze rodičovskou složku, a nově vytvořené vnořené složky se také nasdílí. Můžeme tedy zálohy různých webů ukládat zvlášť do složek.
Skript pro nahrávání
Skript jsem psal kvůli zálohám DB, které mohou mít desítky či stovky MB. Proto je ve skriptu využito dělené nahrávání. Velikost chunku si určíme a poté se do paměti načte pouze menší část. Pokud ale víte, že nikdy nebudete nahrávat velké soubory, využijte jednodušší skript níže.
Může se stát, že API bude vyhazovat výjimku o chybě SSL certifikátu, jak se zbavit této chyby je pospáno v článku Přechod z GApi v2 na v3.
Úpravy oproti APIv2
Hlavní změnou je využití autoloaderu od Composeru a nová inicializace třídy Google_Client více o tom také v článk o Přechodu z APIv2 na APIv3. U třídy pro práci se souborem se metoda změnila z setTitle na setName, rodičovská složka se nastavuje přímo pomocí ID složky, nikoli přes instanci Google_Service_Drive_ParentReference, soubor se vytváří pomocí metody create a ne insert. V metodě listFiles je potřeba pro získání více informací o souborech vyplnit pole fields a samotné získání souborů se provádí přes metodu getFiles, nikoli getItems.
// Využít autoloader od Composeru // require_once 'Google/autoload.php'; // Soubor pro nahrání $fileToUpload = "backup.zip"; // Název souboru na GDrive $uploadedName = "backup - ".date("Y-m-d H:i:s").'.zip'; // Soubory starší 30 dnů se smažou z Drive $olderThan = time() - 60 * 60 * 24 * 30; // Minimálně 20 souborů po smazání musí na Drive zůstat, i když jsou starší $minFiles = 20; // U APIv3 se nepoužívá - Emailová adresa service účtu // $emailAddress = 'yyy@xxx.iam.gserviceaccount.com'; // Cesta k privátnímu klíči - APIv2 používalo klíč ve formátu P12 $keyFileLocation = 'aaa.json'; // ID složky, do které nahráváme zálohy, musí být sdílena se servisním účtem $folderId = '0*************************g'; // Počet bytů po kolika soubor odesíláme $chunkSizeBytes = 5 * 1024 * 1024; $client = new Google_Client(); $client->setApplicationName("BackupDrive"); try{ // ############################ // Inicializace Google Clienta // ############################ // Nový způsob - porovnání v článku Přechod z GApi v2 na v3 $client->setAuthConfig($keyFileLocation); $client->useApplicationDefaultCredentials(); $client->addScope([ \Google_Service_Drive::DRIVE, \Google_Service_Drive::DRIVE_METADATA ]); $service = new \Google_Service_Drive($client); // ################### // Nahrávání souboru // ################### $file = new Google_Service_Drive_DriveFile(); $file->setName($uploadedName); // V APIv2 setTitle $client->setDefer(true); // Neprovádíme operaci ihned // Přiřadíme rodiče k souboru, aby se zařadil do požadované složky // V APIv2 bylo nutné přiřazovat rodiče pomocí // instancí třídy Google_Service_Drive_ParentReference $file->setParents(array($folderId)); // Vytvoříme soubor a nahrajeme // V APIv2 se používala metoda insert $createdFile = $service->files->create($file); $contentType = mime_content_type($fileToUpload); $media = new Google_Http_MediaFileUpload( $client, $createdFile, $contentType, null, // data true, // resumable $chunkSizeBytes ); $media->setFileSize(filesize($fileToUpload)); // Nahráváme postupně po menších částech $status = false; $result = false; $handle = fopen($fileToUpload, "rb"); while (!$status && !feof($handle)) { $chunk = fread($handle, $chunkSizeBytes); $status = $media->nextChunk($chunk); } if($status != false) { $result = $status; } fclose($handle); $client->setDefer(false); }catch(Exception $e){ file_put_contents("error.log", date("Y-m-d H:i:s").": ".$e->getMessage()."\n\n", FILE_APPEND); die(); } // ####################### // Mazání starých souborů // ####################### $newer = 0; $toDelete = array(); // Pouze soubory v požadované složce $files = array(); try{ $files = $service->files->listFiles(array( 'q' => "'{$folderId}' in parents", // Nutné v APIv3 specifikovat, jaké informace o souboru chceme stáhnout 'fields' => 'files(id, name, createdTime)', 'pageSize' => 150, // Kolik souborů zobrazit, default 100, max 1000 ))->getFiles(); // V APIv2 metoda getItems() }catch(Exception $e){ file_put_contents("error.log", date("Y-m-d H:i:s").": ".$e->getMessage()."\n\n", FILE_APPEND); die(); } foreach ($files as $fileObject) { $creationTime = strtotime($fileObject->createdTime); if ($creationTime <= $olderThan) { $toDelete[] = $fileObject->id; }else{ $newer++; } } if ($newer > $minFiles) { foreach ($toDelete as $delId) { try{ $service->files->delete($delId); }catch(Exception $e){ file_put_contents("error.log", date("Y-m-d H:i:s").": ".$e->getMessage()."\n\n", FILE_APPEND); } } }
Skript pro nahrávání menších souborů
V tomto jednodušším skriptu se celý soubor nahraje do paměti a poté se odesílá. Pokud víme, že nikdy nebudeme nahrávat extrémně velké soubory, můžeme si skript zjednodušit. Nahraďte celou část Nahrávání souboru až po konec try bloku.
$file = new Google_Service_Drive_DriveFile(); $file->setName($uploadedName); // V APIv2 setTitle() $mimeType = mime_content_type($backup_filename); $file->setMimeType($mimeType); // Přiřadíme rodiče k souboru, aby se zařadil do požadované složky // V APIv2 bylo nutné přiřazovat rodiče pomocí // instancí třídy Google_Service_Drive_ParentReference $file->setParents(array($folderId)); // V APIv2 se volala metoda insert $createdFile = $service->files->create($file, array( 'data' => file_get_contents($backup_filename), 'mimeType' => $mimeType, 'uploadType' => "multipart" ));
Komplikace a nejasnosti
- Pokud soubor nezařadíme do složky, kterou sdílíme se svým účtem, k souborům se nedostaneme jinak než přes API.
- Soubory se nezapočítávají do úložiště našeho účtu ale účtu Service account. Servisní účet má také 15GB a využití případně maximální velikost lze zjistit pomocí příkazu:
// APIv2 $info = $service->about->get(); echo $info->quotaBytesTotal; echo $info->quotaBytesUsed; // APIv3 - dokumentace velmi vázne v tomto $items = "limit,usage,usageInDrive,usageInDriveTrash"; $info = $service->about->get([ 'fields' => "storageQuota({$items})", ]); print_r($info->getStorageQuota());
- Vlastník souboru je Service account, pokud soubor vymažeme přes klienta GDrive, smaže se jen od nás, ale zůstane pro Service account stále viditelný. Opravdové smazání lze provést pouze přes API opět.
- API vyhodí chybu, pokud se pokusíme změnit vlastníka na sebe. Odpověď je, že tato možnost není implementována, ale pracuje se na ni. Jenže tuto hlášku to vrací již pár let, a pracují na APIv3, takže to už asi implementované nikdy nebude.
- APIv3 je tady, a prozatím změnit vlastníka stále nelze, vrací stejnou chybu. Metoda zatím není naimplementována...
Máte zkušenosti s Google Drive API nebo Vám tento článek přišel vhod? Podělte se s námi v komentářích
K tomuto článku již není možné přidávat další komentáře
Komentáře
Moc děkuji za návod - velice mi pomohl.
Jinak problémy se kterými jsem se setkal já:
- require_once 'Google/autoload.php'; - je google/google-api-php-client z GitHubu
- Dále byl proveden nějaký refactor nad metodami v nové Google API:
$file->setTitle je nově $file->setName,
místo:
$parent = new Google_Service_Drive_ParentReference();
$parent->setId($folderId);
$file->setParents(array($parent));
se uvádí, že stačí:
$file->setParents(array($folderId));
Zdravím,
vďaka za návod, pomohol.
Všetko fungovalo až do doby, keď sa zaplnilo celých 15 GB priestoru - problém je ten, že sa nevymazávali staré súbory. Neviem kde hľadať chybu.. Poradíte?
Jsem rád, že návod pomohl.
Problém se zaplněním může být ze 2 důvodů, které mě teď napadají.
1. Je možné, že $olderThan je příliš velká, tj uchovávají se soubory strašně dlouho. Případně soubory jsou velké, takže i když musí zůstat uchováno x souborů, již ty překročí 15GB.
2. Metoda listFiles vrací v defaultním chování pouze 100 souborů. Je li tedy na Drive více souborů, nemusí je vrátit všechny. To lze zvednout pomocí parametru "pageSize". Již jsem do návodu doplnil.
Pokud ani jeden přístup nepomůže, tak bych si kód debugoval. Zjistil jaké soubory jsou navráceny a případně jakou návratovou hodnotu má funkce mazání.
Aktuálně skript používám na několika webech a všude se soubory mažou, nyní jsem to zkontroloval.
Ďakujem za reakciu.
k bodu 1:
$olderThan = time() - 60 * 60 * 24 * 6;
$minFiles = 96;
skúšal som aj:
$olderThan = time() - 1800;
$minFiles = 3;
- stále namazalo
k bodu 2:
6 dní * 24 hodín * 15 min. = 576 súborov - žeby toto bol problém? Ale prečo nefungovalo druhé nastavenie?
Zistil som ešte, že ak vymažem súbory z GD cez web (vo svojej zložke, ktorá je zdieľaná so servisným účtom), tak na servisnom účte ostávajú stále.
Na priblíženie: skript používam na zálohovanie snímok, ktoré posiela IP kamera na server, je doplnený tak, že každých 15 min. sú snímky otočené o 180° (kvôli fyzickej polohe kamery) potom zbalené do *.zip a odoslané na GD, na koniec sa odoslané súbory vymažú zo servera. Skript prebehol vždy celý, ale súbory na GD ostávali až do teraz, keď už skript nezbehne (skončí pri nahrávaní) kvôli zaplnenému priestoru.. Mám to otestované tak, že snímky za 6 dní by mali zaberať cca 60 - 70% priestoru..
Pomohlo by mi nakopnutie, aké funkcie použiť k zobrazeniu návratových hodnôt? Vďaka
Prvně bych zkusil si vypsat výsledek $service->files->listFiles([...])->getFiles();
Třeba tam bude jen 100 nejnovějších souborů, které se tím pádem nesmažou nikdy. Pokud ne, tak si vypsat soubory po cyklu, které by se měly smazat. A pokud i to bude OK, tak zjistit navrácenou hodnotu funkce $service->files->delete($delId);
Neukládá se nic do souboru error.log?
Jinak smazání souborů přes svůj GoogleDrive nelze, viz sekce Komplikace a nejasnosti. Zde píšu, že to nelze jinak než přes API.
Ospravedlňujem sa, nechtiac sa mi podarilo odoslať to isté...
Nie som si istý, či to robím správne, ale
- výsledkom výpisu $service->files->listFiles([...])->getFiles(); je prázdne pole
- výpis súborov na zmazanie po cykle - neviem ako na to :(
- $service->files->delete($delId); tu sa mi vrátilo toto:
Notice: Undefined variable: delId in ..../gd.php on line 88
Fatal error: Uncaught exception 'Google_Exception' with message '(delete) missing required param: 'fileId'' in ..../google-api-php-client/src/Google/Service/Resource.php:165
Stack trace:
#0 ..../google-api-php-client/vendor/google/apiclient-services/src/Google/Service/Drive/Resource/Files.php(100): Google_Service_Resource->call('delete', Array)
#1 ..../mazanie-gd.php(88): Google_Service_Drive_Resource_Files->delete(NULL)
#2 {main}
thrown in ..../google-api-php-client/src/Google/Service/Resource.php on line 165
Pomocí toho chunkovaného uploadu se mi nedaří získat ID nahraného souboru...
S normálním uploadem jde udělat $file["id"] nebo $file->getId(), ale tady to vrací NULL?
Už jsem na to přišel — musí se zavolat $client->execute($createdFile) a to vrátí objekt i s IDčkem.