/*
 * tclfiles.c -- part of filesys.mod
 *   Tcl stubs for file system commands moved here to support modules
 */
/*
 * Copyright (C) 1997 Robey Pointer
 * Copyright (C) 1999 - 2025 Eggheads Development Team
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

static int tcl_getdesc STDVAR
{
  char *s = NULL;

  BADARGS(3, 3, " dir file");

  filedb_getdesc(argv[1], argv[2], &s);
  if (s) {
    Tcl_AppendResult(irp, s, NULL);
    my_free(s);
    return TCL_OK;
  } else {
    Tcl_AppendResult(irp, "filedb access failed", NULL);
    return TCL_ERROR;
  }
}

static int tcl_setdesc STDVAR
{
  BADARGS(4, 4, " dir file desc");

  filedb_setdesc(argv[1], argv[2], argv[3]);
  return TCL_OK;
}

static int tcl_getowner STDVAR
{
  char *s = NULL;

  BADARGS(3, 3, " dir file");

  filedb_getowner(argv[1], argv[2], &s);
  if (s) {
    Tcl_AppendResult(irp, s, NULL);
    my_free(s);
    return TCL_OK;
  } else {
    Tcl_AppendResult(irp, "filedb access failed", NULL);
    return TCL_ERROR;
  }
}

static int tcl_setowner STDVAR
{
  BADARGS(4, 4, " dir file owner");

  filedb_setowner(argv[1], argv[2], argv[3]);
  return TCL_OK;
}

static int tcl_getgots STDVAR
{
  int i;
  char s[12];

  BADARGS(3, 3, " dir file");

  i = filedb_getgots(argv[1], argv[2]);
  snprintf(s, sizeof s, "%d", i);
  Tcl_AppendResult(irp, s, NULL);
  return TCL_OK;
}

static int tcl_setlink STDVAR
{
  BADARGS(4, 4, " dir file link");

  filedb_setlink(argv[1], argv[2], argv[3]);
  return TCL_OK;
}

static int tcl_getlink STDVAR
{
  char *s = NULL;

  BADARGS(3, 3, " dir file");

  filedb_getlink(argv[1], argv[2], &s);
  if (s) {
    Tcl_AppendResult(irp, s, NULL);
    return TCL_OK;
  } else {
    Tcl_AppendResult(irp, "filedb access failed", NULL);
    return TCL_ERROR;
  }
}

static int tcl_setpwd STDVAR
{
  int i, idx;

  BADARGS(3, 3, " idx dir");

  i = atoi(argv[1]);
  idx = findanyidx(i);
  if ((idx < 0) || (dcc[idx].type != &DCC_FILES)) {
    Tcl_AppendResult(irp, "invalid idx", NULL);
    return TCL_ERROR;
  }
  files_setpwd(idx, argv[2]);

  return TCL_OK;
}

static int tcl_getpwd STDVAR
{
  int i, idx;

  BADARGS(2, 2, " idx");

  i = atoi(argv[1]);
  idx = findanyidx(i);
  if ((idx < 0) || (dcc[idx].type != &DCC_FILES)) {
    Tcl_AppendResult(irp, "invalid idx", NULL);
    return TCL_ERROR;
  }
  Tcl_AppendResult(irp, dcc[idx].u.file->dir, NULL);

  return TCL_OK;
}

static int tcl_getfiles STDVAR
{
  BADARGS(2, 2, " dir");

  filedb_getfiles(irp, argv[1]);
  return TCL_OK;
}

static int tcl_getdirs STDVAR
{
  BADARGS(2, 2, " dir");

  filedb_getdirs(irp, argv[1]);
  return TCL_OK;
}

static int tcl_hide STDVAR
{
  BADARGS(3, 3, " dir file");

  filedb_change(argv[1], argv[2], FILEDB_HIDE);
  return TCL_OK;
}

static int tcl_unhide STDVAR
{
  BADARGS(3, 3, " dir file");

  filedb_change(argv[1], argv[2], FILEDB_UNHIDE);
  return TCL_OK;
}

static int tcl_share STDVAR
{
  BADARGS(3, 3, " dir file");

  filedb_change(argv[1], argv[2], FILEDB_SHARE);
  return TCL_OK;
}

static int tcl_unshare STDVAR
{
  BADARGS(3, 3, " dir file");

  filedb_change(argv[1], argv[2], FILEDB_UNSHARE);
  return TCL_OK;
}

static int tcl_setflags STDVAR
{
  FILE *fdb;
  filedb_entry *fdbe;
  char *s = NULL, *p, *d;

  BADARGS(3, 4, " dir ?flags ?channel??");

  malloc_strcpy(s, argv[1]);
  if (s[strlen(s) - 1] == '/')
    s[strlen(s) - 1] = 0;
  p = strrchr(s, '/');
  if (p == NULL) {
    p = s;
    d = "";
  } else {
    *p = 0;
    p++;
    d = s;
  }

  fdb = filedb_open(d, 0);
  if (!fdb) {
    Tcl_AppendResult(irp, "-3", NULL);  /* filedb access failed */
    my_free(s);
    return TCL_OK;
  }
  filedb_readtop(fdb, NULL);
  fdbe = filedb_matchfile(fdb, ftell(fdb), p);
  my_free(s);

  if (!fdbe) {
    Tcl_AppendResult(irp, "-1", NULL);  /* No such dir */
    return TCL_OK;
  }
  if (!(fdbe->stat & FILE_DIR)) {
    Tcl_AppendResult(irp, "-2", NULL);  /* Not a dir */
    return TCL_OK;
  }
  if (argc >= 3) {
    struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
    char f[100];

    break_down_flags(argv[2], &fr, NULL);
    build_flags(f, &fr, NULL);
    malloc_strcpy_nocheck(fdbe->flags_req, f);
  } else
    my_free(fdbe->flags_req);
  if (argc == 4)
    malloc_strcpy(fdbe->chan, argv[3]);

  filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
  free_fdbe(&fdbe);
  filedb_close(fdb);
  Tcl_AppendResult(irp, "0", NULL);
  return TCL_OK;
}

static int tcl_getflags STDVAR
{
  filedb_entry *fdbe;
  char *s = NULL, *p, *d;

  BADARGS(2, 2, " dir");

  malloc_strcpy(s, argv[1]);
  if (s[strlen(s) - 1] == '/')
    s[strlen(s) - 1] = 0;
  p = strrchr(s, '/');
  if (p == NULL) {
    p = s;
    d = "";
  } else {
    *p = 0;
    p++;
    d = s;
  }

  fdbe = filedb_getentry(d, p);
  /* Directory doesn't exist? */
  if (!fdbe || !(fdbe->stat & FILE_DIR)) {
    Tcl_AppendResult(irp, "", NULL);
    my_free(s);
    free_fdbe(&fdbe);
    return TCL_OK;
  }
  if (fdbe->flags_req) {
    malloc_strcpy(s, fdbe->flags_req);
    if (s[0] == '-')
      s[0] = 0;
  } else
    s[0] = 0;
  Tcl_AppendElement(irp, s);
  Tcl_AppendElement(irp, fdbe->chan);
  my_free(s);
  free_fdbe(&fdbe);
  return TCL_OK;
}

static int tcl_mkdir STDVAR
{
  FILE *fdb;
  filedb_entry *fdbe;
  char *s = NULL, *t, *d, *p;
  struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };

  BADARGS(2, 4, " dir ?required-flags ?channel??");

  malloc_strcpy(s, argv[1]);

  if (s[strlen(s) - 1] == '/')
    s[strlen(s) - 1] = 0;

  p = strrchr(s, '/');
  if (p == NULL) {
    p = s;
    d = "";
  } else {
    *p = 0;
    p++;
    d = s;
  }

  fdb = filedb_open(d, 0);
  if (!fdb) {
    Tcl_AppendResult(irp, "-3", NULL);  /* filedb access failed */
    my_free(s);
    return TCL_OK;
  }
  filedb_readtop(fdb, NULL);
  fdbe = filedb_matchfile(fdb, ftell(fdb), p);

  if (!fdbe) {
    t = nmalloc(strlen(dccdir) + strlen(d) + strlen(p) + 2);
    sprintf(t, "%s%s/%s", dccdir, d, p);
    if (mkdir(t, 0755) != 0) {
      Tcl_AppendResult(irp, "1", NULL);
      my_free(t);
      my_free(s);
      filedb_close(fdb);
      return TCL_OK;
    }
    fdbe = malloc_fdbe();
    fdbe->stat = FILE_DIR;
    malloc_strcpy(fdbe->filename, argv[1]);
    fdbe->uploaded = now;
  } else if (!(fdbe->stat & FILE_DIR)) {
    Tcl_AppendResult(irp, "2", NULL);
    free_fdbe(&fdbe);
    my_free(s);
    filedb_close(fdb);
    return TCL_OK;
  }
  if (argc >= 3) {
    char f[100];

    break_down_flags(argv[2], &fr, NULL);
    build_flags(f, &fr, NULL);
    malloc_strcpy_nocheck(fdbe->flags_req, f);
  } else if (fdbe->flags_req) {
    my_free(fdbe->flags_req);
  }
  if (argc == 4) {
    malloc_strcpy(fdbe->chan, argv[3]);
  } else if (fdbe->chan)
    my_free(fdbe->chan);

  if (!fdbe->pos)
    fdbe->pos = POS_NEW;
  filedb_updatefile(fdb, fdbe->pos, fdbe, UPDATE_ALL);
  filedb_close(fdb);
  free_fdbe(&fdbe);

  Tcl_AppendResult(irp, "0", NULL);
  return TCL_OK;
}

static int tcl_rmdir STDVAR
{
  FILE *fdb;
  filedb_entry *fdbe;
  char *s = NULL, *t, *d, *p;

  BADARGS(2, 2, " dir");

  malloc_strcpy(s, argv[1]);
  if (s[strlen(s) - 1] == '/')
    s[strlen(s) - 1] = 0;
  p = strrchr(s, '/');
  if (p == NULL) {
    p = s;
    d = "";
  } else {
    *p = 0;
    p++;
    d = s;
  }

  fdb = filedb_open(d, 0);
  if (!fdb) {
    Tcl_AppendResult(irp, "1", NULL);
    my_free(s);
    return TCL_OK;
  }
  filedb_readtop(fdb, NULL);
  fdbe = filedb_matchfile(fdb, ftell(fdb), p);

  if (!fdbe) {
    Tcl_AppendResult(irp, "1", NULL);
    filedb_close(fdb);
    my_free(s);
    return TCL_OK;
  }
  if (!(fdbe->stat & FILE_DIR)) {
    Tcl_AppendResult(irp, "1", NULL);
    filedb_close(fdb);
    free_fdbe(&fdbe);
    my_free(s);
    return TCL_OK;
  }
  /* Erase '.filedb' and '.files' if they exist */
  t = nmalloc(strlen(dccdir) + strlen(d) + strlen(p) + 11);
  sprintf(t, "%s%s/%s/.filedb", dccdir, d, p);
  unlink(t);
  sprintf(t, "%s%s/%s/.files", dccdir, d, p);
  unlink(t);
  sprintf(t, "%s%s/%s", dccdir, d, p);
  my_free(s);
  if (rmdir(t) == 0) {
    filedb_delfile(fdb, fdbe->pos);
    filedb_close(fdb);
    free_fdbe(&fdbe);
    my_free(t);
    Tcl_AppendResult(irp, "0", NULL);
    return TCL_OK;
  }
  my_free(t);
  free_fdbe(&fdbe);
  filedb_close(fdb);
  Tcl_AppendResult(irp, "1", NULL);
  return TCL_OK;
}

static int tcl_mv_cp(Tcl_Interp *irp, int argc, char **argv, int copy)
{
  char *p, *fn = NULL, *oldpath = NULL, *s = NULL, *s1 = NULL;
  char *newfn = NULL, *newpath = NULL;
  int ok = 0, only_first, skip_this;
  FILE *fdb_old, *fdb_new;
  filedb_entry *fdbe_old, *fdbe_new;
  long where;

  BADARGS(3, 3, " oldfilepath newfilepath");

  malloc_strcpy(fn, argv[1]);
  p = strrchr(fn, '/');
  if (p != NULL) {
    *p = 0;
    malloc_strcpy(s, fn);
    memmove(fn, p + 1, strlen(p + 1) + 1);
    if (!resolve_dir("/", s, &oldpath, -1)) {
      /* Tcl can do * anything */
      Tcl_AppendResult(irp, "-1", NULL);        /* Invalid source */
      my_free(fn);
      my_free(oldpath);
      return TCL_OK;
    }
    my_free(s);
  } else
    malloc_strcpy(oldpath, "/");
  malloc_strcpy(s, argv[2]);
  if (!resolve_dir("/", s, &newpath, -1)) {
    /* Destination is not just a directory */
    p = strrchr(s, '/');
    if (!p) {
      malloc_strcpy(newfn, s);
      s[0] = 0;
    } else {
      *p = 0;
      malloc_strcpy(newfn, p + 1);
    }
    my_free(newpath);
    if (!resolve_dir("/", s, &newpath, -1)) {
      Tcl_AppendResult(irp, "-2", NULL);        /* Invalid desto */
      my_free(newpath);
      my_free(s);
      my_free(newfn);
      return TCL_OK;
    }
  } else
    malloc_strcpy(newfn, "");
  my_free(s);

  /* Stupidness checks */
  if ((!strcmp(oldpath, newpath)) && (!newfn[0] || !strcmp(newfn, fn))) {
    my_free(newfn);
    my_free(fn);
    my_free(oldpath);
    my_free(newpath);
    Tcl_AppendResult(irp, "-3", NULL);  /* Stupid copy to self */
    return TCL_OK;
  }
  /* Be aware of 'cp * this.file' possibility: ONLY COPY FIRST ONE */
  if ((strchr(fn, '?') || strchr(fn, '*')) && newfn[0])
    only_first = 1;
  else
    only_first = 0;

  fdb_old = filedb_open(oldpath, 0);
  if (!strcmp(oldpath, newpath))
    fdb_new = fdb_old;
  else
    fdb_new = filedb_open(newpath, 0);
  if (!fdb_old || !fdb_new) {
    my_free(newfn);
    my_free(fn);
    my_free(oldpath);
    my_free(newpath);
    if (fdb_old)
      filedb_close(fdb_old);
    else if (fdb_new)
      filedb_close(fdb_new);
    Tcl_AppendResult(irp, "-5", NULL);  /* DB access failed */
    return -1;
  }

  filedb_readtop(fdb_old, NULL);
  fdbe_old = filedb_matchfile(fdb_old, ftell(fdb_old), fn);
  if (!fdbe_old) {
    my_free(newfn);
    my_free(fn);
    my_free(oldpath);
    my_free(newpath);
    if (fdb_new != fdb_old)
      filedb_close(fdb_new);
    filedb_close(fdb_old);
    Tcl_AppendResult(irp, "-4", NULL);  /* No match */
    return -2;
  }
  while (fdbe_old) {
    where = ftell(fdb_old);
    skip_this = 0;
    if (!(fdbe_old->stat & (FILE_HIDDEN | FILE_DIR))) {
      s = nmalloc(strlen(dccdir) + strlen(oldpath)
                  + strlen(fdbe_old->filename) + 2);
      s1 = nmalloc(strlen(dccdir) + strlen(newpath)
                   + strlen(newfn[0] ? newfn : fdbe_old->filename) + 2);
      sprintf(s, "%s%s%s%s", dccdir, oldpath,
              oldpath[0] ? "/" : "", fdbe_old->filename);
      sprintf(s1, "%s%s%s%s", dccdir, newpath,
              newpath[0] ? "/" : "", newfn[0] ? newfn : fdbe_old->filename);
      if (!strcmp(s, s1)) {
        Tcl_AppendResult(irp, "-3", NULL);      /* Stupid copy to self */
        skip_this = 1;
      }
      /* Check for existence of file with same name in new dir */
      filedb_readtop(fdb_new, NULL);
      fdbe_new = filedb_matchfile(fdb_new, ftell(fdb_new),
                                  newfn[0] ? newfn : fdbe_old->filename);
      if (fdbe_new) {
        /* It's ok if the entry in the new dir is a normal file (we'll
         * just scrap the old entry and overwrite the file) -- but if
         * it's a directory, this file has to be skipped.
         */
        if (fdbe_new->stat & FILE_DIR)
          skip_this = 1;
        else
          filedb_delfile(fdb_new, fdbe_new->pos);
        free_fdbe(&fdbe_new);
      }
      if (!skip_this) {
        if ((fdbe_old->sharelink) ||
            ((copy ? copyfile(s, s1) : movefile(s, s1)) == 0)) {
          /* Raw file moved okay: create new entry for it */
          ok++;
          fdbe_new = malloc_fdbe();
          fdbe_new->stat = fdbe_old->stat;
          /* We don't have to worry about any entries to be
           * NULL, because malloc_strcpy takes care of that.
           */
          malloc_strcpy(fdbe_new->flags_req, fdbe_old->flags_req);
          malloc_strcpy(fdbe_new->chan, fdbe_old->chan);
          malloc_strcpy(fdbe_new->filename, fdbe_old->filename);
          malloc_strcpy(fdbe_new->desc, fdbe_old->desc);
          if (newfn[0])
            malloc_strcpy(fdbe_new->filename, newfn);
          malloc_strcpy(fdbe_new->uploader, fdbe_old->uploader);
          fdbe_new->uploaded = fdbe_old->uploaded;
          fdbe_new->size = fdbe_old->size;
          fdbe_new->gots = fdbe_old->gots;
          malloc_strcpy(fdbe_new->sharelink, fdbe_old->sharelink);
          filedb_addfile(fdb_new, fdbe_new);
          if (!copy)
            filedb_delfile(fdb_old, fdbe_old->pos);
          free_fdbe(&fdbe_new);
        }
      }
      my_free(s);
      my_free(s1);
    }
    free_fdbe(&fdbe_old);
    fdbe_old = filedb_matchfile(fdb_old, where, fn);
    if (ok && only_first) {
      free_fdbe(&fdbe_old);
    }
  }
  if (fdb_old != fdb_new)
    filedb_close(fdb_new);
  filedb_close(fdb_old);
  if (!ok)
    Tcl_AppendResult(irp, "-4", NULL);  /* No match */
  else {
    char x[30];

    sprintf(x, "%d", ok);
    Tcl_AppendResult(irp, x, NULL);
  }
  my_free(newfn);
  my_free(fn);
  my_free(oldpath);
  my_free(newpath);
  return TCL_OK;
}

static int tcl_mv STDVAR
{
  return tcl_mv_cp(irp, argc, argv, 0);
}

static int tcl_cp STDVAR
{
  return tcl_mv_cp(irp, argc, argv, 1);
}

static int tcl_fileresend_send(ClientData cd, Tcl_Interp *irp, int argc,
                               char *argv[], int resend)
{
  int i, idx;
  char s[21];

  BADARGS(3, 4, " idx filename ?nick?");

  i = atoi(argv[1]);
  idx = findanyidx(i);
  if (idx < 0 || dcc[idx].type != &DCC_FILES) {
    Tcl_AppendResult(irp, "invalid idx", NULL);
    return TCL_ERROR;
  }
  if (argc == 4)
    i = files_reget(idx, argv[2], argv[3], resend);
  else
    i = files_reget(idx, argv[2], "", resend);
  sprintf(s, "%d", i);
  Tcl_AppendResult(irp, s, NULL);
  return TCL_OK;
}

static int tcl_fileresend STDVAR
{
  return tcl_fileresend_send(cd, irp, argc, argv, 1);
}

static int tcl_filesend STDVAR
{
  return tcl_fileresend_send(cd, irp, argc, argv, 0);
}

static tcl_cmds mytcls[] = {
  {"getdesc",    tcl_getdesc},
  {"getowner",   tcl_getowner},
  {"setdesc",    tcl_setdesc},
  {"setowner",   tcl_setowner},
  {"getgots",    tcl_getgots},
  {"getpwd",     tcl_getpwd},
  {"setpwd",     tcl_setpwd},
  {"getlink",    tcl_getlink},
  {"setlink",    tcl_setlink},
  {"getfiles",   tcl_getfiles},
  {"getdirs",    tcl_getdirs},
  {"hide",       tcl_hide},
  {"unhide",     tcl_unhide},
  {"share",      tcl_share},
  {"unshare",    tcl_unshare},
  {"filesend",   tcl_filesend},
  {"fileresend", tcl_fileresend},
  {"mkdir",      tcl_mkdir},
  {"rmdir",      tcl_rmdir},
  {"cp",         tcl_cp},
  {"mv",         tcl_mv},
  {"getflags",   tcl_getflags},
  {"setflags",   tcl_setflags},
  {NULL,         NULL}
};
