- add '-d' option to susetags2solv, reads all packages files
authorMichael Schroeder <mls@suse.de>
Mon, 3 Mar 2008 17:11:40 +0000 (17:11 +0000)
committerMichael Schroeder <mls@suse.de>
Mon, 3 Mar 2008 17:11:40 +0000 (17:11 +0000)
- add '-b' option to susetags2solv and rpmdb2solv
- both options currently a bit experimental

tools/CMakeLists.txt
tools/common_write.c
tools/repo_susetags.c
tools/repo_susetags.h
tools/rpmdb2solv.c
tools/susetags2solv.c

index 5485737..14658e4 100644 (file)
@@ -24,7 +24,7 @@ TARGET_LINK_LIBRARIES( helix2solv satsolver ${EXPAT_LIBRARY})
 
 SET(susetags2solv_REPOS susetags2solv.c repo_susetags.h repo_susetags.c repo_content.c repo_write.c common_write.c)
 ADD_EXECUTABLE( susetags2solv ${susetags2solv_REPOS} )
-TARGET_LINK_LIBRARIES( susetags2solv satsolver)
+TARGET_LINK_LIBRARIES( susetags2solv satsolver ${ZLIB_LIBRARY})
 
 SET(patchxml2solv_REPOS patchxml2solv.c repo_patchxml.h repo_patchxml.c repo_write.c common_write.c)
 ADD_EXECUTABLE( patchxml2solv ${patchxml2solv_REPOS} )
index 01fdda7..89d117a 100644 (file)
 #include "repo_write.h"
 #include "common_write.h"
 
-static char *verticals[] = {
-  "authors",
-  "description",
-  "messagedel",
-  "messageins",
-  "eula",
-  "diskusage",
-  "filelist",
+static Id verticals[] = {
+  SOLVABLE_AUTHORS,
+  SOLVABLE_DESCRIPTION,
+  SOLVABLE_MESSAGEDEL,
+  SOLVABLE_MESSAGEINS,
+  SOLVABLE_EULA,
+  SOLVABLE_DISKUSAGE,
+  SOLVABLE_FILELIST,
   0
 };
 
-static unsigned char *filter;
-static int nfilter;
-
-static void
-create_filter(Pool *pool)
-{
-  char **s;
-  Id id;
-  for (s = verticals; *s; s++)
-    {
-      id = str2id(pool, *s, 1);
-      if (id >= nfilter)
-       {
-         filter = sat_realloc(filter, id + 16);
-         memset(filter + nfilter, 0, id + 16 - nfilter);
-         nfilter = id + 16;
-       }
-      filter[id] = 1;
-    }
-}
+static char *languagetags[] = {
+  "solvable:summary:",
+  "solvable:description:",
+  "solvable:messageins:",
+  "solvable:messagedel:",
+  0
+};
 
 static int test_separate = 0;
 
 static int
 keyfilter_solv(Repo *data, Repokey *key, void *kfdata)
 {
+  int i;
   if (test_separate && key->storage != KEY_STORAGE_SOLVABLE)
     return KEY_STORAGE_DROPPED;
-  if (key->name < nfilter && filter[key->name])
-    return KEY_STORAGE_VERTICAL_OFFSET;
+  for (i = 0; verticals[i]; i++)
+    if (key->name == verticals[i])
+      return KEY_STORAGE_VERTICAL_OFFSET;
   return KEY_STORAGE_INCORE;
 }
 
 static int
 keyfilter_attr(Repo *data, Repokey *key, void *kfdata)
 {
+  int i;
   if (key->storage == KEY_STORAGE_SOLVABLE)
     return KEY_STORAGE_DROPPED;
-  if (key->name < nfilter && filter[key->name])
-    return KEY_STORAGE_VERTICAL_OFFSET;
+  for (i = 0; verticals[i]; i++)
+    if (key->name == verticals[i])
+      return KEY_STORAGE_VERTICAL_OFFSET;
+  return KEY_STORAGE_INCORE;
+}
+
+static int
+keyfilter_language(Repo *repo, Repokey *key, void *kfdata)
+{
+  const char *name, *p;
+  char *lang = kfdata, *bname;
+  int i;
+  Id id;
+
+  name = id2str(repo->pool, key->name);
+  p = strrchr(name, ':');
+  if (!p || strcmp(p + 1, lang) != 0)
+    return KEY_STORAGE_DROPPED;
+  /* find base name id */
+  bname = strdup(name);
+  bname[p - name] = 0;
+  id = str2id(repo->pool, bname, 1);
+  for (i = 0; verticals[i]; i++)
+    if (id == verticals[i])
+      return KEY_STORAGE_VERTICAL_OFFSET;
+  return KEY_STORAGE_INCORE;
+}
+
+static int
+keyfilter_DU(Repo *repo, Repokey *key, void *kfdata)
+{
+  int i;
+  if (key->name != SOLVABLE_DISKUSAGE)
+    return KEY_STORAGE_DROPPED;
+  for (i = 0; verticals[i]; i++)
+    if (key->name == verticals[i])
+      return KEY_STORAGE_VERTICAL_OFFSET;
+  return KEY_STORAGE_INCORE;
+}
+
+static int
+keyfilter_FL(Repo *repo, Repokey *key, void *kfdata)
+{
+  int i;
+  if (key->name != SOLVABLE_FILELIST)
+    return KEY_STORAGE_DROPPED;
+  for (i = 0; verticals[i]; i++)
+    if (key->name == verticals[i])
+      return KEY_STORAGE_VERTICAL_OFFSET;
+  return KEY_STORAGE_INCORE;
+}
+
+struct keyfilter_other_data {
+  char **languages;
+  int nlanguages;
+};
+
+static int
+keyfilter_other(Repo *repo, Repokey *key, void *kfdata)
+{
+  const char *name, *p;
+  struct keyfilter_other_data *kd = kfdata;
+  int i;
+
+  if (key->name == SOLVABLE_FILELIST || key->name == SOLVABLE_DISKUSAGE)
+    return KEY_STORAGE_DROPPED;
+  name = id2str(repo->pool, key->name);
+  p = strrchr(name, ':');
+  if (p)
+    {
+      for (i = 0; i < kd->nlanguages; i++)
+       if (!strcmp(p + 1, kd->languages[i]))
+         return KEY_STORAGE_DROPPED;
+    }
+  for (i = 0; verticals[i]; i++)
+    if (key->name == verticals[i])
+      return KEY_STORAGE_VERTICAL_OFFSET;
   return KEY_STORAGE_INCORE;
 }
 
 /*
  * Write <repo> to stdout
  * If <attrname> is given, write attributes to <attrname>
+ * If <basename> is given, split attributes
  */
 
 int
 tool_write(Repo *repo, const char *basename, const char *attrname)
 {
-  Pool *pool = repo->pool;
-  Repodatafile fileinfoa[1];
-  Repodatafile *fileinfo = 0;
-  int nsubfiles = 0;
+  Repodata *data;
+  Repokey *key;
+  Repodatafile *fileinfos = 0;
+  int nfileinfos = 0;
+  char **languages = 0;
+  int nlanguages = 0;
+  int i, j, k, l;
+
+  if (basename)
+    {
+      struct keyfilter_other_data kd;
+      char fn[4096];
+      FILE *fp;
+      int has_DU = 0;
+      int has_FL = 0;
 
-  create_filter(pool);
-  memset (fileinfoa, 0, sizeof fileinfoa);
+      /* find languages and other info */
+      for (i = 0, data = repo->repodata; i < repo->nrepodata; i++, data++)
+       {
+         for (j = 1, key = data->keys + j; j < data->nkeys; j++, key++)
+           {
+             const char *keyname = id2str(repo->pool, key->name);
+             if (key->name == SOLVABLE_DISKUSAGE)
+               has_DU = 1;
+             if (key->name == SOLVABLE_FILELIST)
+               has_FL = 1;
+             for (k = 0; languagetags[k] != 0; k++)
+               if (!strncmp(keyname, languagetags[k], strlen(languagetags[k])))
+                 break;
+             if (!languagetags[k])
+               continue;
+             l = strlen(languagetags[k]);
+             if (strlen(keyname + l) > 5)
+               continue;
+             for (k = 0; k < nlanguages; k++)
+               if (!strcmp(languages[k], keyname + l))
+                 break;
+             if (k < nlanguages)
+               continue;
+             languages = sat_realloc2(languages, nlanguages + 1, sizeof(char *));
+             languages[nlanguages++] = strdup(keyname + l);
+           }
+       }
+      fileinfos = sat_calloc(nlanguages + 2, sizeof(Repodatafile));
+      /* write language subfiles */
+      for (i = 0; i < nlanguages; i++)
+        {
+         sprintf(fn, "%s.%s.solv", basename, languages[i]);
+         if (!(fp = fopen(fn, "w")))
+           {
+             perror(fn);
+             exit(1);
+           }
+          repo_write(repo, fp, keyfilter_language, languages[i], fileinfos + nfileinfos, 0);
+         fileinfos[nfileinfos++].location = strdup(fn);
+         fclose(fp);
+        }
+      /* write DU subfile */
+      if (has_DU)
+       {
+         sprintf(fn, "%s.DU.solv", basename);
+         if (!(fp = fopen(fn, "w")))
+           {
+             perror(fn);
+             exit(1);
+           }
+         repo_write(repo, fp, keyfilter_DU, 0, fileinfos + nfileinfos, 0);
+         fileinfos[nfileinfos++].location = strdup(fn);
+         fclose(fp);
+       }
+      /* write filelist */
+      if (has_FL)
+       {
+         sprintf(fn, "%s.FL.solv", basename);
+         if (!(fp = fopen(fn, "w")))
+           {
+             perror(fn);
+             exit(1);
+           }
+         repo_write(repo, fp, keyfilter_FL, 0, fileinfos + nfileinfos, 0);
+         fileinfos[nfileinfos++].location = strdup(fn);
+         fclose(fp);
+       }
+      /* write everything else */
+      sprintf(fn, "%s.solv", basename);
+      if (!(fp = fopen(fn, "w")))
+       {
+         perror(fn);
+         exit(1);
+       }
+      kd.languages = languages;
+      kd.nlanguages = nlanguages;
+      repo_write(repo, fp, keyfilter_other, &kd, fileinfos, nfileinfos);
+      fclose(fp);
+      for (i = 0; i < nlanguages; i++)
+       free(languages[i]);
+      sat_free(languages);
+      for (i = 0; i < nfileinfos; i++)
+       {
+         sat_free(fileinfos[i].location);
+         sat_free(fileinfos[i].keys);
+       }
+      sat_free(fileinfos);
+      exit(0);
+    }
   if (attrname)
     {
+      fileinfos = sat_calloc(1, sizeof(Repodatafile));
       test_separate = 1;
-      fileinfo = fileinfoa;
       FILE *fp = fopen (attrname, "w");
-      repo_write(repo, fp, keyfilter_attr, 0, fileinfo, 0);
+      repo_write(repo, fp, keyfilter_attr, 0, fileinfos + nfileinfos++, 0);
+      fileinfos[nfileinfos - 1].location = strdup(attrname);
       fclose(fp);
-      fileinfo->location = strdup(attrname);
-      fileinfo++;
-
-      nsubfiles = fileinfo - fileinfoa;
-      fileinfo = fileinfoa;
     }
-  repo_write(repo, stdout, keyfilter_solv, 0, fileinfo, nsubfiles);
-  if (fileinfo)
+  repo_write(repo, stdout, keyfilter_solv, 0, fileinfos, nfileinfos);
+  if (fileinfos)
     {
-      free(fileinfo->location);
-      free(fileinfo->keys);
+      free(fileinfos[0].location);
+      free(fileinfos[0].keys);
+      free(fileinfos);
     }
-  sat_free(filter);
   return 0;
 }
index 0c5b76d..8057797 100644 (file)
@@ -27,6 +27,7 @@ struct parsedata {
   int nshare;
   Id (*dirs)[3]; // dirid, size, nfiles
   int ndirs;
+  Id langcache[ID_NUM_INTERNAL];
 };
 
 static char *flagtab[] = {
@@ -38,6 +39,26 @@ static char *flagtab[] = {
   "<="
 };
 
+
+static Id
+langtag(struct parsedata *pd, Id tag, const char *language)
+{
+  char *p;
+  const char *tagname;
+
+  if (!language || tag >= ID_NUM_INTERNAL)
+    return tag;
+  if (!pd->langcache[tag])
+    {
+      tagname = id2str(pd->repo->pool, tag);
+      p = sat_malloc(strlen(tagname) + strlen(language) + 2);
+      sprintf(p, "%s:%s", tagname, language);
+      pd->langcache[tag] = str2id(pd->repo->pool, p, 1);
+      sat_free(p);
+    }
+  return pd->langcache[tag];
+}
+
 /*
  * adddep
  * create and add dependency
@@ -238,7 +259,7 @@ commit_diskusage (struct parsedata *pd, unsigned entry)
   /* Now sort in dirid order.  This ensures that parents come before
      their children.  */
   if (pd->ndirs > 1)
-    qsort (pd->dirs, pd->ndirs, sizeof (pd->dirs[0]), id3_cmp);
+    qsort(pd->dirs, pd->ndirs, sizeof (pd->dirs[0]), id3_cmp);
   /* Substract leaf numbers from all parents to make the numbers
      non-cumulative.  This must be done post-order (i.e. all leafs
      adjusted before parents).  We ensure this by starting at the end of
@@ -334,7 +355,7 @@ tag_from_string (char *cs)
  */
 
 void
-repo_add_susetags(Repo *repo, FILE *fp, Id vendor, int flags)
+repo_add_susetags(Repo *repo, FILE *fp, Id vendor, const char *language, int flags)
 {
   Pool *pool = repo->pool;
   char *line, *linep;
@@ -348,7 +369,14 @@ repo_add_susetags(Repo *repo, FILE *fp, Id vendor, int flags)
   struct parsedata pd;
   Repodata *data = 0;
 
-  data = repo_add_repodata(repo);
+  if ((flags & SUSETAGS_EXTEND) && repo->nrepodata)
+    {
+      /* use last repodata */
+      data = repo->repodata + repo->nrepodata - 1;
+      indesc = 1;
+    }
+  if (!data)
+    data = repo_add_repodata(repo);
 
   memset(&pd, 0, sizeof(pd));
   line = malloc(1024);
@@ -562,7 +590,7 @@ repo_add_susetags(Repo *repo, FILE *fp, Id vendor, int flags)
         solvables.  */
       if (indesc >= 2 && !s)
         {
-         fprintf (stderr, "Huh?\n");
+         fprintf (stderr, "Huh %s?\n", line);
           continue;
        }
       switch (tag)
@@ -686,19 +714,19 @@ repo_add_susetags(Repo *repo, FILE *fp, Id vendor, int flags)
            repodata_set_str(data, last_found_pack, SOLVABLE_AUTHORS, line + 6);
            continue;
           case CTAG('=', 'S', 'u', 'm'):
-           repodata_set_str(data, last_found_pack, SOLVABLE_SUMMARY, line + 6);
+           repodata_set_str(data, last_found_pack, langtag(&pd, SOLVABLE_SUMMARY, language), line + 6);
            continue;
           case CTAG('=', 'D', 'e', 's'):
-           repodata_set_str(data, last_found_pack, SOLVABLE_DESCRIPTION, line + 6);
+           repodata_set_str(data, last_found_pack, langtag(&pd, SOLVABLE_DESCRIPTION, language), line + 6);
            continue;
           case CTAG('=', 'E', 'u', 'l'):
-           repodata_set_str(data, last_found_pack, SOLVABLE_EULA, line + 6);
+           repodata_set_str(data, last_found_pack, langtag(&pd, SOLVABLE_EULA, language), line + 6);
            continue;
           case CTAG('=', 'I', 'n', 's'):
-           repodata_set_str(data, last_found_pack, SOLVABLE_MESSAGEINS, line + 6);
+           repodata_set_str(data, last_found_pack, langtag(&pd, SOLVABLE_MESSAGEINS, language), line + 6);
            continue;
           case CTAG('=', 'D', 'e', 'l'):
-           repodata_set_str(data, last_found_pack, SOLVABLE_MESSAGEDEL, line + 6);
+           repodata_set_str(data, last_found_pack, langtag(&pd, SOLVABLE_MESSAGEDEL, language), line + 6);
            continue;
           case CTAG('=', 'V', 'i', 's'):
            {
@@ -792,3 +820,4 @@ repo_add_susetags(Repo *repo, FILE *fp, Id vendor, int flags)
     free(pd.common.tmp);
   free(line);
 }
+
index 4c6c8d4..880844a 100644 (file)
@@ -10,5 +10,6 @@
  */
 
 #define SUSETAGS_KINDS_SEPARATELY 1
+#define SUSETAGS_EXTEND 2
 
-extern void repo_add_susetags(Repo *repo, FILE *fp, Id vendor, int flags);
+extern void repo_add_susetags(Repo *repo, FILE *fp, Id vendor, const char *language, int flags);
index e818e11..81d58bd 100644 (file)
@@ -31,13 +31,19 @@ main(int argc, char **argv)
   Repo *repo, *ref = 0;
   FILE *fp;
   Pool *refpool;
-  int g;
+  int c;
   const char *root = "/";
+  const char *basefile = 0;
 
-  while ((g = getopt (argc, argv, "r:")) >= 0)
-    switch (g)
+  while ((c = getopt (argc, argv, "b:r:")) >= 0)
+    switch (c)
       {
-      case 'r': root = optarg; break;
+      case 'r':
+        root = optarg;
+        break;
+      case 'b':
+        basefile = optarg;
+        break;
       default:
        exit(1);
       }
@@ -66,7 +72,7 @@ main(int argc, char **argv)
       ref = 0;
     }
 
-  tool_write(repo, 0, 0);
+  tool_write(repo, basefile, 0);
   pool_free(pool);
 
   exit(0);
index 8f71a41..aa9e069 100644 (file)
@@ -5,12 +5,16 @@
  * for further information
  */
 
+#define _GNU_SOURCE
+
 #include <sys/types.h>
 #include <limits.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <dirent.h>
+#include <zlib.h>
 
 #include "pool.h"
 #include "repo.h"
 #include "common_write.h"
 
 static void
-usage(const char *err)
+usage(int status)
 {
-  if (err)
-    fprintf(stderr, "\n** Error:\n  %s\n", err);
   fprintf(stderr, "\nUsage:\n"
           "susetags2solv [-a][-s][-c <content>][-h]\n"
          "  reads a 'susetags' repository from <stdin> and writes a .solv file to <stdout>\n"
          "  -c <contentfile> : parse given contentfile (for product information)\n"
+          "  -d <descrdir> : do not read from stdin, but use data in descrdir\n"
          "  -h : print help & exit\n"
          "  -k : don't mix kinds (experimental!)\n"
+         "  -b <base>: save fas multiple files starting with <base>\n"
          "  -n <name>: save attributes as <name>.attr\n"
         );
-   exit(0);
+   exit(status);
+}
+
+static ssize_t
+cookie_gzread(void *cookie, char *buf, size_t nbytes)
+{
+  return gzread((gzFile *)cookie, buf, nbytes);
+}
+
+static int
+cookie_gzclose(void *cookie)
+{
+  return gzclose((gzFile *)cookie);
+}
+
+FILE *
+myfopen(const char *fn)
+{
+  cookie_io_functions_t cio;
+  char *suf;
+  gzFile *gzf;
+
+  if (!fn)
+    return 0;
+  suf = strrchr(fn, '.');
+  if (!suf || strcmp(suf, ".gz") != 0)
+    return fopen(fn, "r");
+  gzf = gzopen(fn, "r");
+  if (!gzf)
+    return 0;
+  memset(&cio, 0, sizeof(cio));
+  cio.read = cookie_gzread;
+  cio.close = cookie_gzclose;
+  return  fopencookie(gzf, "r", cio);
 }
 
 int
@@ -39,54 +76,48 @@ main(int argc, char **argv)
 {
   const char *contentfile = 0;
   const char *attrname = 0;
+  const char *descrdir = 0;
+  const char *basefile = 0;
   Id vendor = 0;
   int flags = 0;
-  argv++;
-  argc--;
-  while (argc--)
+  int c;
+
+  while ((c = getopt(argc, argv, "hkn:c:d:b:")) >= 0)
     {
-      const char *s = argv[0];
-      if (*s++ == '-')
-        while (*s)
-          switch (*s++)
-           {
-             case 'h': usage(NULL); break;
-             case 'n':
-               if (argc)
-                 {
-                   attrname = argv[1];
-                   argv++;
-                   argc--;
-                 }
-               else
-                 usage("argument required for '-n'");
-               break;
-             case 'c':
-               if (argc)
-                 {
-                   contentfile = argv[1];
-                   argv++;
-                   argc--;
-                 }
-               else
-                 usage("argument required for '-c'");
-               break;
-             case 'k':
-               flags |= SUSETAGS_KINDS_SEPARATELY;
-             break;
-             default : break;
-           }
-      argv++;
+      switch (c)
+       {
+       case 'h':
+         usage(0);
+         break;
+       case 'k':
+         flags |= SUSETAGS_KINDS_SEPARATELY;   /* do not use! */
+         break;
+       case 'n':
+         attrname = optarg;
+         break;
+       case 'c':
+         contentfile = optarg;
+         break;
+       case 'd':
+         descrdir = optarg;
+         break;
+       case 'b':
+         basefile = optarg;
+         break;
+       default:
+         usage(1);
+         break;
+       }
     }
   Pool *pool = pool_create();
-  Repo *repo = repo_create(pool, "<stdin>");
+  Repo *repo = repo_create(pool, "<susetags>");
   if (contentfile)
     {
       FILE *fp = fopen (contentfile, "r");
       if (!fp)
         {
-         perror("opening content file");
-         exit (1);
+         perror(contentfile);
+         exit(1);
        }
       repo_add_content(repo, fp);
       if (!strncmp (id2str(pool, pool->solvables[repo->start].name), "product:", 8))
@@ -100,14 +131,86 @@ main(int argc, char **argv)
       if (!dot || strcmp(dot, ".attr"))
       {
        int len = strlen (attrname);
-       char *newname = (char *)malloc (len + 6); /* alloc for <attrname>+'.attr'+'\0' */
+       char *newname = (char *)malloc(len + 6); /* alloc for <attrname>+'.attr'+'\0' */
        strcpy (newname, attrname);
        strcpy (newname+len, ".attr");
        attrname = newname;
       }
     }
-  repo_add_susetags(repo, stdin, vendor, flags);
-  tool_write(repo, 0, attrname);
+
+  if (descrdir)
+    {
+      char *fnp;
+      int ndirs, i;
+      struct dirent **files;
+
+      ndirs = scandir(descrdir, &files, 0, alphasort);
+      if (ndirs < 0)
+       {
+         perror(descrdir);
+         exit(1);
+       }
+      fnp = sat_malloc(strlen(descrdir) + 128);
+      for (i = 0; i < ndirs; i++)
+       {
+         char *fn = files[i]->d_name;
+
+         if (!strcmp(fn, "packages") || !strcmp(fn, "packages.gz"))
+           {
+             sprintf(fnp, "%s/%s", descrdir, fn);
+             FILE *fp = myfopen(fnp);
+             if (!fp)
+               {
+                 perror(fn);
+                 exit(1);
+               }
+             repo_add_susetags(repo, fp, vendor, 0, flags);
+             fclose(fp);
+           }
+         else if (!strcmp(fn, "packages.DU") || !strcmp(fn, "packages.DU.gz"))
+           {
+             sprintf(fnp, "%s/%s", descrdir, fn);
+             FILE *fp = myfopen(fnp);
+             if (!fp)
+               {
+                 perror(fn);
+                 exit(1);
+               }
+             repo_add_susetags(repo, fp, vendor, 0, flags | SUSETAGS_EXTEND);
+             fclose(fp);
+           }
+         else if (!strncmp(fn, "packages.", 9))
+           {
+             char lang[6];
+             char *p;
+             sprintf(fnp, "%s/%s", descrdir, fn);
+             p = strrchr(fn, '.');
+             if (p && !strcmp(p, ".gz"))
+               {
+                 *p = 0;
+                 p = strrchr(fn, '.');
+               }
+             if (!p || !p[1] || strlen(p + 1) > 5)
+               continue;
+             strcpy(lang, p + 1);
+             sprintf(fnp, "%s/%s", descrdir, fn);
+             FILE *fp = myfopen(fnp);
+             if (!fp)
+               {
+                 perror(fn);
+                 exit(1);
+               }
+             repo_add_susetags(repo, fp, vendor, lang, flags | SUSETAGS_EXTEND);
+             fclose(fp);
+           }
+       }
+      free(files);
+      free(fnp);
+    }
+  else
+    repo_add_susetags(repo, stdin, vendor, 0, flags);
+
+  tool_write(repo, basefile, attrname);
   pool_free(pool);
   exit(0);
 }