unit unubeseable;

{$mode delphi}

interface

(**
   Esta unidad administra el Almacen de Cosas tanto en el CacheLocal como en la Nube.

   Las Cosas que puede almacenar son una cadena de caracteres identificadas por
   un nid_cosa que identifica la Instancia de la cosa y el nid_version que identifica
   la versión de la instancia de la cosa.

**)

uses
  urosx,
  uDataSetGenerico, xmatdefs,
  Classes, SysUtils,
  uauxiliares, u_uploadarchi, uConstantesSimSEE;

const
  // Códigos de error
  EC_fallo_conexion_url_nube = -1;
  EC_no_encontrada_en_la_nube = -2;
  EC_no_encontrada_en_cache = -3;
  EC_no_encontrada_en_ningun_lado = -4;
  EC_usuario_no_autenticado = -5;
  EC_usuario_o_clave_no_validos = -6;
  EC_No_tiene_los_permisos = -7;
  EC_problemas_en_bajar_archivos = -8;


type

  TRecPermisosNube = class

  end;

  { TNube_registro }

  TNube_registro = class
    Data: string;

    nid: integer;
    nid_version: integer;
    dt_creacion: TDateTime;
    comentarios: string;
    usuario: integer;
    grupo: integer;
    nBloques: integer;

    constructor Create(Data: string; nid, nid_version, usuario, grupo: integer;
      dt_creacion: TDateTime; comentarios: string; nBloques: integer);
  end;

  TArr_Nube_registro = array of TNube_registro;

  { TNubeAdmin }

  TNubeAdmin = class
    id_hilo: integer;
    cache_dir: string;
    nube_url: string;
    PASAPORTE: string; // obtenido en el login
    usr: string;
    nid_grupos: string;

    constructor Create(id_hilo: integer; cache_dir, nube_url: string);

    // retorna TRUE si fue exitoso e inicializa el PASAPORTE
    // Llama a la función login del script url_nube y obtiene
    // un pasaporte válido.
    // Todas las funciones contra la nube deben pasar el parámetro
    // psp=PASAPORTE  que será chequedo por el script url_nube antes
    // de proceder a ejecutar.
    function Login(usuario, clave: string): boolean;

    // Llama a la función logut de url_nube y cancela la validéz del
    // PASAPORTE. Vacía la variable PASAPORTE indicando que no está logueado.
    function logout: boolean;

    function Permisos( nid_data: integer): TRecPermisosNube;


    // Inserta una nueva instancia de una cosa y retorna el nid_cosa asignado
    // si tiene éxito. Si se produce un error retorna 0 (cero).
    // La primer instancia de una cosa tiene nid_version = 1
    function WriteToNube(Data, permiso: string; nid_data: integer): integer;

    // baja de la nube el Data correspondiente a (nid, nid_version)
    // retorna vacío si falló y en codErr el error.
    function ReadFromNube(nid_data, nid_version: integer;
      var codErr: integer): string;

    // baja de la nube todos los datas que tengan en data_memo LIKE data
    // retorna vacío si falló y en codErr el error.
    function ReadFromNubeLike(Data: string;
      var codErr: integer): TArr_Nube_registro;

    // escribe en cache el Data, identificado con nid_data y nid_version
    // si ya no existe.
    procedure WriteToCache(nid_data, nid_version: integer; const Data: string);


    //busca en cache el Data correspondiente a (nid, nid_version)
    // retorna vacío si falló y en codErr el error.
    function ReadFromCache(nid_data, nid_version: integer;
      out codErr: integer): string;

    ////////////////////ARCHI/////////////////////////////
    //Inserta un nuevo archivo en la Nube
    // Si hubo un error retorta el errorCod. En nid retorna el nid asignado
    function uploadArchi(var nid, nid_version: integer;
      archi, Data, permiso: string): string;
    // escribe en cache el Archivo, identificado con nid_data y nid_version
    function WriteToCacheArchi(const archi_origen, archi_destino: string;
      nid, nid_version: integer): integer;
    //Descar el Archivo identificado con nid y nid_version
    function Download(tbl_archivo: string; nid, nid_version: integer;
      archi: string; carpetaDestino: string): integer;

    function mismoChecksum(nidArchivo, nbloques: integer): boolean;
    ////////////////////////////////////////////////

    // retornan la última versión ya sea de la nube o del cache y -1 si no encuentran
    function UltimaVersionFromNube(nid_data: integer): integer;
    function UltimaVersionFromCache(nid_data: integer): integer;

    // Escribe a la nube nueva versión del Data (nid_data) y si tienen éxito,
    // retorna un entero positivo con el número de versión asignado.
    // Si hay error retoran un negativo con el código de error.
    function WriteNewVersionToNube(nid_data: integer;
      const Data, permiso: string): integer;

    // retorna el path completo al archivo en el cache (cache_dir)/(nid_data)_v(nid_version).txt
    // solo para Cosas, para los archivos usar TArchiRef_Nubeseable.getarchi
    function ArchiCache(nid_data, nid_version: integer): string;

    // Elimina todo los objetos de cache
    function ClearCache: integer;

    // Elimina todos los objetos de cache asociados a un (nid_data)
    function ClearCacheOf(nid_data: integer): integer;

    // si lo tiene en el cache lo lee de cache sino lo busca en la nube
    // si falla retorna el string vacío y en errCod retorna el código de error
    // si tiene éxito la variable errCod se retora en CERO.
    function GetData(nid_data, nid_version: integer; out errCod: integer): string;


  end;


var
  nubeAdmin: TNubeAdmin;
  dbcon_rosx: TDBrosxCon;


// retorna mensaje de error leible por humano.
function ecToMsg(errCod: integer): string;


implementation

function CrearSiNoExiste(carpeta: string): string;
begin
  if not DirectoryExists(carpeta) then
    try
      mkdir(carpeta)
    except
      on E: Exception do
        raise Exception.Create('Error al intentar crear la carpeta: ' + carpeta);
    end;
  Result := carpeta;
end;

// retorna mensaje de error leible por humano.
function ecToMsg(errCod: integer): string;
begin
  case errCod of
    EC_fallo_conexion_url_nube: Result := 'Falló conexión a la url_nube';
    EC_no_encontrada_en_la_nube: Result := 'Cosa no encontrada en la nube';
    EC_no_encontrada_en_cache: Result := 'Cosa no encontrada en el cache';
    EC_no_encontrada_en_ningun_lado: Result :=
        'Cosa no encontrada ni en la nube, ni en cache';
    EC_usuario_no_autenticado: Result := 'Usuario no autenticado';
    EC_usuario_o_clave_no_validos: Result := 'Usuario o clave no validos';
    EC_No_tiene_los_permisos: Result := 'No tiene los permisos para realizar el insert';
    EC_problemas_en_bajar_archivos: Result := 'Hubo problemas intentando bajar archivos';
    else
      Result := 'ErrCod: ' + IntToStr(errCod) + ' DESCONOCIDO!.';
  end;
end;

{ TNube }

constructor TNube_registro.Create(Data: string;
  nid, nid_version, usuario, grupo: integer; dt_creacion: TDateTime;
  comentarios: string; nBloques: integer);
begin
  self.Data := Data;
  self.nid := nid;
  self.nid_version := nid_version;
  self.usuario := usuario;
  self.grupo := grupo;
  self.dt_creacion := FloatToDateTime(dt_creacion);
  self.comentarios := comentarios;
  self.nBloques := nBloques;
end;



{ TNubeAdmin }

constructor TNubeAdmin.Create(id_hilo: integer; cache_dir, nube_url: string);
begin
  self.id_hilo := id_hilo;
  self.cache_dir := cache_dir;
  self.nube_url := nube_url;
end;

function TNubeAdmin.Login(usuario, clave: string): boolean;
var
  usuario_nid: string;
  ds: TResultadoQuery;
begin

  self.usr := usuario;
  self.nid_grupos := 'poner grupo';
  Result := False;
  PASAPORTE := '';
  ds := dbcon_rosx.sql_query('select nid from usuarios_simsee where nombre=''' +
    usuario + '''');
  if not ds.EOF then
  begin
    usuario_nid := IntToStr(ds.FieldByName('nid').AsInteger);
    PASAPORTE := dbcon_rosx.sql_login(usuario_nid, clave);
  end;

  if PASAPORTE <> '' then
    Result := True;

end;

function TNubeAdmin.logout: boolean;
begin
  Result := dbcon_rosx.sql_logout(PASAPORTE);
end;

function TNubeAdmin.Permisos(nid_data: integer): TRecPermisosNube;
begin
  if nid_data > 0 then
    // obtener los permisos
     result:= nil // por ahora hasta que se implemente
  else
     result:= nil;
end;

function TNubeAdmin.WriteToNube(Data, permiso: string; nid_data: integer): integer;
var
  nid_version: integer;

begin

  nid_version := 1; //es una nueva versión

  Result := dbcon_rosx.sql_exec_pass(PASAPORTE, Data, nid_version, nid_data, 0, permiso);

  if Result <= 0 then
  begin
    if Result = -2 then
    begin
      Result := EC_usuario_o_clave_no_validos;
    end
    else
      Result := EC_fallo_conexion_url_nube;
  end;

end;

function TNubeAdmin.ReadFromNube(nid_data, nid_version: integer;
  var codErr: integer): string;
var
  ds: TResultadoQuery;
  r: TDataRecord;
begin

  ds := dbcon_rosx.sql_query_pass(PASAPORTE, nid_data, nid_version, '');

  if ds.EOF then
  begin
    codErr := EC_no_encontrada_en_la_nube;
    Result := '';
  end
  else
  begin
    if ds.nid = -2 then
    begin
      Result := '';
      codErr := EC_fallo_conexion_url_nube;
      exit;
    end;

    r := ds.First;
    codErr := 0;  //0: ok
    Result := r.GetByNameAsString('data_memo');

  end;
end;

function TNubeAdmin.ReadFromNubeLike(Data: string;
  var codErr: integer): TArr_Nube_registro;
var
  ds: TResultadoQuery;
  r: TDataRecord;

  k: integer;
  arr_nube: TArr_Nube_registro;
begin

  ds := dbcon_rosx.sql_query_pass(PASAPORTE, 0, 0, Data);

  if ds.EOF then
  begin
    codErr := EC_no_encontrada_en_la_nube;
    exit;
  end
  else
  begin
    SetLength(arr_nube, ds.nrows);
    if ds.nid = -2 then
    begin
      Result := arr_nube;
      codErr := EC_fallo_conexion_url_nube;
      exit;
    end;

    k := 0;
    r := ds.First;
    while not ds.EOF do
    begin
      codErr := 0;  //0: ok
      arr_nube[k] := TNube_registro.Create(r.GetByNameAsString('data_memo'),
        r.GetByNameAsInt('nid'), r.GetByNameAsInt('nid_version'),
        r.GetByNameAsInt('usuario'), r.GetByNameAsInt('grupo'),
        r.FieldByName('dt_creacion').AsDateTime,
        r.GetByNameAsString('comentarios'), r.GetByNameAsInt('nbloques'));
      Inc(k);
      r := ds.Next;
    end;
  end;

  Result := arr_nube;

end;

procedure TNubeAdmin.WriteToCache(nid_data, nid_version: integer;
  const Data: string);
var
  f: TextFile;
  archi: string;
begin
  archi := self.ArchiCache(nid_data, nid_version);
  if not fileexists(archi) then
  begin
    AssignFile(f, archi);
    Rewrite(f);
    WriteLn(f, Data);
    CloseFile(f);
  end;
end;

function TNubeAdmin.ReadFromCache(nid_data, nid_version: integer; out
  codErr: integer): string;
var
  f: TextFile;
  archi, linea, memo: string;
begin
  archi := self.ArchiCache(nid_data, nid_version);
  memo := '';
  if fileexists(archi) then
  begin
    AssignFile(f, archi);
    reset(f);
    while not EOF(f) do
    begin
      readLn(f, linea);
      memo := memo + linea + #13;
    end;
    Close(f);
    Result := memo;
    codErr := 0;
  end
  else
  begin
    codErr := EC_no_encontrada_en_cache;
    Result := '';
  end;
end;


function TNubeAdmin.uploadArchi(var nid, nid_version: integer;
  archi, Data, permiso: string): string;
var
  zipname: string;
  nidarchivo: integer;
  ext_str: string;
  lstArchis: TStringList;
begin
  //nidarchivo := dbcon_rosx.sql_nextnidarchivo_pass('nube_simsee',PASAPORTE);

  if not FileExists(archi) then
    raise Exception.Create('No se encuentra el archivo de sala: ' + archi)
  else
  begin
    ext_str := ExtractFileExt(archi);
    begin
      lstArchis := TStringList.Create;
      lstArchis.add(archi);
      zipname := ChangeFileExt(archi, '');
      if ext_str <> '.zip' then
      begin
        if FileExists(zipName + '.zip') then
          deletefile(zipName + '.zip');
      end;
      if zipearListaDeArchivos(lstArchis, zipName) <> 1 then
        raise Exception.Create('No fue posible comprimir el archivo.');
      zipname := zipname + '.zip';
    end;

    writeln('Subiendo archivo empaquetado.');
    upload_archi(dbcon_rosx, 'nube_archi_bloques', nid, nid_version, nidarchivo, zipname
      , archi, Data, permiso, PASAPORTE);
    if nid > 0 then
      writeln('El archivo fue subido exitosamente. Nid asignado: ' + IntToStr(nid))
    else
      raise Exception.Create('Falló la subida del archivo. ' +
        u_uploadarchi.UltimoError);

    Result := u_uploadarchi.UltimoError;

  end;
end;

function TNubeAdmin.WriteToCacheArchi(const archi_origen, archi_destino: string;
  nid, nid_version: integer): integer;
begin
  Result := 1;
  if (not (FileExists(archi_destino))) then
    if (not (FileExists(archi_origen))) then
      Result := Download('nube_archi_bloques', nid, nid_version,
        archi_destino, uConstantesSimSEE.getDir_CacheNube)
    else
      cp(archi_origen, archi_destino);
end;

function TNubeAdmin.Download( tbl_archivo: string; nid, nid_version: integer;
  archi: string; carpetaDestino: string): integer;
var
  ok: boolean;
begin

  if self.PASAPORTE = '' then
  begin
    Result := EC_usuario_no_autenticado;
    exit;
  end;

  ok := bajarCarpeta(dbcon_rosx, tbl_archivo, nid,
    nid_version, self.PASAPORTE, archi, carpetaDestino);
  if ok then
    Result := 1
  else
    Result := EC_problemas_en_bajar_archivos;
end;

function TNubeAdmin.mismoChecksum(nidArchivo, nbloques: integer): boolean;
var
  ds_Bloques: TResultadoQuery;
  rBloque: TDataRecord;
  orden: string;
  chkSumRemoto, chkSumLocal: array of ShortString;
  k_bloque: integer;
  bytes_restantes: integer;
  flg_error: boolean;
  nbytes: integer;
  fb: file of byte;
  arch: string;
begin
  orden := 'SELECT nid, checksum, nombre_archi FROM nube_archi_bloques ' +
    ' WHERE nid_archivo = ' + IntToStr(nidArchivo) +
    ' ORDER BY nid desc, kbloque LIMIT ' + IntToStr(nbloques);
  ds_Bloques := dbcon_rosx.query(orden);
  rBloque := ds_Bloques.First;

  SetLength(chkSumRemoto, nbloques);
  SetLength(chkSumLocal, nbloques);

  //checksum Local
  assignfile(fb, rBloque.GetByNameAsString('nombre_archi'));
  reset(fb);
  nbytes := system.filesize(fb);

  k_bloque := 0;
  bytes_restantes := nbytes;
  flg_error := False;
  while (bytes_restantes > 0) and not flg_error do
  begin
    if bytes_restantes > MAX_FILE_BLOCK then
      nbytes := MAX_FILE_BLOCK
    else
      nbytes := bytes_restantes;

    setlength(arch, nbytes);
    blockread(fb, arch[1], nbytes);
    chkSumLocal[k_bloque] := checksum(@arch[1], Length(arch));
    Inc(k_bloque);
  end;

  k_bloque := 0;
  //checksum Remoto
  while not ds_Bloques.EOF do
  begin
    chkSumRemoto[k_bloque] := rBloque.GetByNameAsString('checksum');

    if chkSumLocal[k_bloque] = chkSumRemoto[k_bloque] then
      Result := True
    else
    begin
      Result := False;
      exit;
    end;
    rBloque := ds_bloques.Next;

    Inc(k_bloque);
  end;
end;

function TNubeAdmin.UltimaVersionFromNube(nid_data: integer): integer;

var
  ds: TResultadoQuery;
  r: TDataRecord;

begin

  ds := dbcon_rosx.sql_query_pass(PASAPORTE, nid_data, 1, '');

  if ds.EOF then
    Result := -1111 //no hay última versión del archivo solicitado en la nube
  else
  begin
    if ds.nid = -2 then
    begin
      Result := -1111;//error
      exit;
    end;
    r := ds.First;
    Result := r.GetByNameAsInt('nid_version');
  end;

end;

function TNubeAdmin.UltimaVersionFromCache(nid_data: integer): integer;
var
  knid_version, codErr: integer;
begin
  knid_version := 0;
  codErr := 0;

  while codErr = 0 do
  begin
    Inc(knid_version);
    ReadFromCache(nid_data, knid_version, codErr);
  end;

  if knid_version = 1 then
    Result := -1111
  else
    Result := knid_version - 1;

end;

function TNubeAdmin.WriteNewVersionToNube(nid_data: integer;
  const Data, permiso: string): integer;
var
  nid_version, res: integer;
begin

  //Ultima versión
  nid_version := self.UltimaVersionFromNube(nid_data);
  nid_version := nid_version + 1; //uno mas de la que había

  res := dbcon_rosx.sql_exec_pass(PASAPORTE, Data, nid_version, nid_data, 0, permiso);

  if res > 0 then
    Result := nid_version
  else
  begin
    if res = -2 then
    begin
      Result := EC_usuario_o_clave_no_validos;
    end
    else
      Result := EC_fallo_conexion_url_nube;
  end;

end;

function TNubeAdmin.ArchiCache(nid_data, nid_version: integer): string;
var
  carpeta_data, archivo: string;

begin
  carpeta_data := CrearSiNoExiste(getDir_CacheNube);
  archivo := carpeta_data + DirectorySeparator + IntToStr(nid_data) +
    '_v' + IntToStr(nid_version) + '.txt';

  Result := archivo;
end;



function TNubeAdmin.ClearCache: integer;
var
  SR: TSearchRec;
begin
  if FindFirst(getDir_CacheNube + '*.*', faArchive {+ faHidden} + faReadOnly, SR) = 0 then
    repeat
      DeleteFile(getDir_CacheNube + SR.Name);     //NO FUNCIONA, ARREGLAR
    until FindNext(SR) <> 0;
  Result := 0; // que devuelvo??
end;

function TNubeAdmin.ClearCacheOf(nid_data: integer): integer;
var
  archi: string;
  ult_version, k: integer;
begin

  ult_version := self.UltimaVersionFromCache(nid_data);

  for k := 1 to ult_version do
  begin
    archi := self.ArchiCache(nid_data, k);
    if fileexists(archi) then
    begin
      DeleteFile(archi);
    end;
  end;

  Result := 0; // que devuelvo??

end;

function TNubeAdmin.GetData(nid_data, nid_version: integer; out errCod: integer
  ): string;
var
  memo: string;

begin
  memo := '';
  //fijarse primero en el cache si el actor se encuentra allí, sino
  //bajar de la nube

  memo := ReadFromCache(nid_data, nid_version, errCod);
  if errCod = EC_no_encontrada_en_cache then
    memo := ReadFromNube(nid_data, nid_version, errCod);

  if errCod = EC_no_encontrada_en_la_nube then
    errCod := EC_no_encontrada_en_ningun_lado;

  Result := memo;

end;



initialization
{$IFDEF NUBE}
  nubeAdmin := TNubeAdmin.Create(0, getDir_CacheNube, '/zeros/x/rosx_prueba.php');
  dbcon_rosx := TDBrosxCon.Create('', 'latorre.adme.com.uy', '80',
    '/zeros/x/rosx_prueba.php');
  dbcon_rosx.determinar_tipo_servidor;
{$ENDIF}

finalization
{$IFDEF NUBE}
  if dbcon_rosx <> nil then
    dbcon_rosx.Free;
{$ENDIF}
end.



