<?php
namespace nur\b\io;

use nur\file;
use ZipArchive;

/**
 * Class ZipStreamer: classe outil pour faciliter la construction dynamique d'un
 * fichier zip qui est streamé vers l'utilisateur
 */
class ZipBuilder {
  const TMPDIR = null;

  function __construct(string $filename) {
    $tmpzip = file::temp("zipf", static::TMPDIR);
    $zip = new ZipArchive();
    if ($zip->open($tmpzip, ZipArchive::CREATE) !== true) {
      throw new IOException("unable to create $tmpzip");
    }
    $this->filename = $filename;
    $this->tmpzip = $tmpzip;
    $this->zip = $zip;
    $this->deletes = [$tmpzip];
  }

  private $filename;
  private $tmpzip;
  private $zip;
  private $deletes;

  /**
   * ajouter une entrée dans le fichier zip avec le nom spécifié
   *
   * un fichier temporaire est préparé, et une instance permettant d'y écrire
   * est retournée. c'est ce fichier qui sera inséré dans le fichier zip.
   */
  function newFile(string $entryname): IWriter {
    $tmpfile = file::temp("zipe", static::TMPDIR);
    $this->deletes[] = $tmpfile;
    $outw = new FileWriter($tmpfile, "wb");
    if ($this->zip->addFile($tmpfile, $entryname) !== true) {
      $outw->close();
      throw new IOException("unable to add $entryname");
    }
    return $outw;
  }

  /**
   * ajouter une entrée dans le fichier zip construite à partir du fichier
   * spécifié. ce fichier doit exister jusqu'à ce que la méthode {@link send()}
   * soit appelée
   */
  function addFile(string $file, ?string $entryname=null): void {
    if ($entryname === null) $entryname = basename($file);
    if ($this->zip->addFile($file, $entryname) !== true) {
      throw new IOException("unable to add $file");
    }
  }

  /**
   * ajouter une entrée dans le fichier zip construite à partir de la chaine
   * spécifiée
   */
  function addString(string $entryname, string $content): void {
    if ($this->zip->addFromString($entryname, $content) !== true) {
      throw new IOException("unable to add $entryname");
    }
  }

  /** finaliser le fichier zip et le streamer vers le client */
  function send(): void {
    $this->zip->close();
    $zipf = file::open($this->tmpzip, "rb");
    foreach ($this->deletes as $delete) {
      unlink($delete);
    }
    header("Content-Type: application/zip");
    header("Content-Disposition: attachment; filename=\"$this->filename\"");
    fpassthru($zipf);
  }
}
