Esempio n. 1
0
const char *_camera_import_request_image_filename(const dt_camera_t *camera,const char *filename,void *data)
{
  dt_camera_import_t *t = (dt_camera_import_t *)data;
  t->vp->filename=filename;

  gchar* fixed_path = dt_util_fix_path(t->path);
  g_free(t->path);
  t->path = fixed_path;
  dt_variables_expand( t->vp, t->path, FALSE );
  const gchar *storage=dt_variables_get_result(t->vp);

  dt_variables_expand( t->vp, t->filename, TRUE );
  const gchar *file = dt_variables_get_result(t->vp);

  // Start check if file exist if it does, increase sequence and check again til we know that file doesnt exists..
  gchar *fullfile=g_build_path(G_DIR_SEPARATOR_S,storage,file,(char *)NULL);
  if( g_file_test(fullfile, G_FILE_TEST_EXISTS) == TRUE )
  {
    do
    {
      g_free(fullfile);
      dt_variables_expand( t->vp, t->filename, TRUE );
      file = dt_variables_get_result(t->vp);
      fullfile=g_build_path(G_DIR_SEPARATOR_S,storage,file,(char *)NULL);
    }
    while( g_file_test(fullfile, G_FILE_TEST_EXISTS) == TRUE);
  }

  return file;
}
Esempio n. 2
0
const char *
dt_import_session_filename(struct dt_import_session_t *self, gboolean current)
{
  const char *path;
  char *fname, *previous_fname;
  char *pattern;

  if (current && self->current_filename != NULL)
    return self->current_filename;

  /* expand next filename */
  g_free((void *)self->current_filename);
  pattern = _import_session_filename_pattern();
  if (pattern == NULL)
  {
    fprintf(stderr,"[import_session] Failed to get session filaname pattern.\n");
    return NULL;
  }

  dt_variables_expand(self->vp, pattern, TRUE);

  /* verify that expanded path and filename yields a unique file */
  path = dt_import_session_path(self, TRUE);
  previous_fname = fname = g_build_path(G_DIR_SEPARATOR_S, path,
                                        dt_variables_get_result(self->vp), (char *)NULL);
  if (g_file_test(fname, G_FILE_TEST_EXISTS) == TRUE)
  {
    fprintf(stderr, "[import_session] File %s exists.\n", fname);
    do
    {
      /* file exists, yield a new filename */
      dt_variables_expand(self->vp, pattern, TRUE);
      fname = g_build_path(G_DIR_SEPARATOR_S, path,
                           dt_variables_get_result(self->vp), (char *)NULL);

      fprintf(stderr, "[import_session] Testing %s.\n", fname);
      /* check if same filename was yielded as before */
      if (strcmp(previous_fname ,fname) == 0)
      {
        g_free(previous_fname);
        g_free(fname);
        dt_control_log(_("couldn't expand to a unique filename for session, please check your import session settings."));
        return NULL;
      }

      g_free(previous_fname);
      previous_fname = fname;

    } while (g_file_test(fname, G_FILE_TEST_EXISTS) == TRUE);
  }

  g_free(previous_fname);
  g_free(pattern);

  self->current_filename = g_strdup(dt_variables_get_result(self->vp));
  fprintf(stderr, "[import_session] Using filename %s.\n", self->current_filename);

  return self->current_filename;
}
Esempio n. 3
0
const char *_camera_import_request_image_filename(const dt_camera_t *camera,const char *filename,void *data)
{
  dt_camera_import_t *t = (dt_camera_import_t *)data;
  t->vp->filename=filename;

  gchar* fixed_path = dt_util_fix_path(t->path);
  g_free(t->path);
  t->path = fixed_path;
  dt_variables_expand( t->vp, t->path, FALSE );
  gchar *storage = dt_variables_get_result(t->vp);

  dt_variables_expand( t->vp, t->filename, TRUE );
  gchar *file = dt_variables_get_result(t->vp);

  // Start check if file exist if it does, increase sequence and check again til we know that file doesnt exists..
  gchar *prev_filename;
  gchar *fullfile;
  prev_filename = fullfile = g_build_path(G_DIR_SEPARATOR_S,
					  storage, file,
					  (char *)NULL);

  if( g_file_test(fullfile, G_FILE_TEST_EXISTS) == TRUE )
  {
    do
    {
      dt_variables_expand( t->vp, t->filename, TRUE );
      g_free(file);
      file = dt_variables_get_result(t->vp);
      fullfile = g_build_path(G_DIR_SEPARATOR_S, storage, file, (char *)NULL);

      // if we expanded to same filename the variables are wrong and ${SEQUENCE}
      // is probably missing...
      if (strcmp(prev_filename, fullfile) == 0)
      {
	if (prev_filename != fullfile)
	  g_free(prev_filename);

	g_free(fullfile);
	g_free(storage);

	dt_control_log(_("Couldn't expand to a uniq filename for import, please check your import settings."));

	return NULL;
      }

      g_free(prev_filename);
      prev_filename = fullfile;
    }
    while( g_file_test(fullfile, G_FILE_TEST_EXISTS) == TRUE);
  }

  if (prev_filename != fullfile)
    g_free(prev_filename);

  g_free(fullfile);
  g_free(storage);
  return file;
}
Esempio n. 4
0
/** Listener interface for import job */
void _camera_image_downloaded(const dt_camera_t *camera,const char *filename,void *data)
{
  // Import downloaded image to import filmroll
  dt_camera_import_t *t = (dt_camera_import_t *)data;
  dt_film_image_import(t->film,filename, FALSE);
  dt_control_log(_("%d/%d imported to %s"), t->import_count+1,g_list_length(t->images), g_path_get_basename(filename));

  t->fraction+=1.0/g_list_length(t->images);

  dt_control_backgroundjobs_progress(darktable.control, t->bgj, t->fraction );

  if( dt_conf_get_bool("plugins/capture/camera/import/backup/enable") == TRUE )
  {
    // Backup is enable, let's initialize a backup job of imported image...
    char *base=dt_conf_get_string("plugins/capture/storage/basedirectory");
    char *fixed_base=dt_util_fix_path(base);
    dt_variables_expand( t->vp, fixed_base, FALSE );
    g_free(base);
    const char *sdpart=dt_variables_get_result(t->vp);
    if( sdpart )
    {
      // Initialize a image backup job of file
      dt_job_t j;
      dt_camera_import_backup_job_init(&j, filename,filename+strlen(sdpart));
      dt_control_add_job(darktable.control, &j);
    }
  }
  t->import_count++;
}
Esempio n. 5
0
const char *
dt_import_session_path(struct dt_import_session_t *self, gboolean current)
{
  char *pattern;
  char *new_path;

  if (current && self->current_path != NULL)
    return self->current_path;

  /* check if expanded path differs from current */
  pattern = _import_session_path_pattern();
  if (pattern == NULL)
  {
    fprintf(stderr,"[import_session] Failed to get session path pattern.\n");
    return NULL;
  }

  dt_variables_expand(self->vp, pattern, FALSE);
  new_path = g_strdup(dt_variables_get_result(self->vp));
  g_free(pattern);

  /* did the session path change ? */
  if (self->current_path && strcmp(self->current_path, new_path) == 0)
    return self->current_path;

  /* we need to initialize a new filmroll for the new path */
  if (_import_session_initialize_filmroll(self, new_path) != 0)
  {
    fprintf(stderr, "[import_session] Failed to get session path.\n");
    return NULL;
  }

  return self->current_path;
}
Esempio n. 6
0
static void
_update_example(_camera_import_dialog_t *dialog)
{
  // create path/filename and execute a expand..
  gchar *path=g_build_path(G_DIR_SEPARATOR_S,dialog->settings.basedirectory->value,dialog->settings.subdirectory->value,"/",(char *)NULL);
  gchar *fixed_path=dt_util_fix_path(path);
  dt_variables_expand( dialog->vp, fixed_path, FALSE);

  gchar *ep=g_strdup(dt_variables_get_result(dialog->vp));
  dt_variables_expand( dialog->vp, dialog->settings.namepattern->value, TRUE);
  gchar *ef=g_strdup(dt_variables_get_result(dialog->vp));

  gchar *str=g_strdup_printf("%s\n%s",ep,ef);

  // then set result set
  gtk_label_set_text(GTK_LABEL(dialog->settings.example),str);
  // Cleanup
  g_free(path);
  g_free(fixed_path);
  g_free(ep);
  g_free(ef);
  g_free(str);
}
Esempio n. 7
0
const gchar *_capture_view_get_session_filename(const dt_view_t *view,const char *filename)
{
  g_assert( view != NULL );
  dt_capture_t *cv=(dt_capture_t *)view->data;

  cv->vp->filename = filename;

  gchar* fixed_path=dt_util_fix_path(cv->path);
  g_free(cv->path);
  cv->path = fixed_path;
  dt_variables_expand( cv->vp, cv->path, FALSE );
  gchar *storage = g_strdup(dt_variables_get_result(cv->vp));

  dt_variables_expand( cv->vp, cv->filenamepattern, TRUE );
  gchar *file = g_strdup(dt_variables_get_result(cv->vp));

  // Start check if file exist if it does, increase sequence and check again til we know that file doesnt exists..
  gchar *fullfile = g_build_path(G_DIR_SEPARATOR_S,storage,file,(char *)NULL);

  if( g_file_test(fullfile, G_FILE_TEST_EXISTS) == TRUE )
  {
    do
    {
      g_free(fullfile);
      g_free(file);
      dt_variables_expand( cv->vp, cv->filenamepattern, TRUE );
      file = g_strdup(dt_variables_get_result(cv->vp));
      fullfile=g_build_path(G_DIR_SEPARATOR_S,storage,file,(char *)NULL);
    }
    while( g_file_test(fullfile, G_FILE_TEST_EXISTS) == TRUE);
  }

  g_free(fullfile);
  g_free(storage);

  return file;
}
Esempio n. 8
0
int
store (dt_imageio_module_data_t *sdata, const int imgid, dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total)
{
  dt_imageio_disk_t *d = (dt_imageio_disk_t *)sdata;

  char filename[1024]= {0};
  char dirname[1024]= {0};
  dt_image_full_path(imgid, dirname, 1024);
  int fail = 0;
  // we're potentially called in parallel. have sequence number synchronized:
  dt_pthread_mutex_lock(&darktable.plugin_threadsafe);
  {

    // if filenamepattern is a directory just let att ${FILE_NAME} as default..
    if ( g_file_test(d->filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) || ((d->filename+strlen(d->filename))[0]=='/' || (d->filename+strlen(d->filename))[0]=='\\') )
      snprintf (d->filename+strlen(d->filename), 1024-strlen(d->filename), "$(FILE_NAME)");

    // avoid braindead export which is bound to overwrite at random:
    if(total > 1 && !g_strrstr(d->filename, "$"))
    {
      snprintf(d->filename+strlen(d->filename), 1024-strlen(d->filename), "_$(SEQUENCE)");
    }

    gchar* fixed_path = dt_util_fix_path(d->filename);
    g_strlcpy(d->filename, fixed_path, 1024);
    g_free(fixed_path);

    d->vp->filename = dirname;
    d->vp->jobcode = "export";
    d->vp->imgid = imgid;
    d->vp->sequence = num;
    dt_variables_expand(d->vp, d->filename, TRUE);
    g_strlcpy(filename, dt_variables_get_result(d->vp), 1024);
    g_strlcpy(dirname, filename, 1024);

    const char *ext = format->extension(fdata);
    char *c = dirname + strlen(dirname);
    for(; c>dirname && *c != '/'; c--);
    if(*c == '/') *c = '\0';
    if(g_mkdir_with_parents(dirname, 0755))
    {
      fprintf(stderr, "[imageio_storage_disk] could not create directory: `%s'!\n", dirname);
      dt_control_log(_("could not create directory `%s'!"), dirname);
      fail = 1;
      goto failed;
    }

    c = filename + strlen(filename);
    for(; c>filename && *c != '.' && *c != '/' ; c--);
    if(c <= filename || *c=='/') c = filename + strlen(filename);

    sprintf(c,".%s",ext);

    /* prevent overwrite of files */
    int seq=1;
failed:
    if (!fail && g_file_test (filename,G_FILE_TEST_EXISTS))
    {
      do
      {
        sprintf(c,"_%.2d.%s",seq,ext);
        seq++;
      }
      while (g_file_test (filename,G_FILE_TEST_EXISTS));
    }

  } // end of critical block
  dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);
  if(fail) return 1;

  /* export image to file */
  dt_imageio_export(imgid, filename, format, fdata);

  /* now write xmp into that container, if possible */
  dt_exif_xmp_attach(imgid, filename);

  printf("[export_job] exported to `%s'\n", filename);
  char *trunc = filename + strlen(filename) - 32;
  if(trunc < filename) trunc = filename;
  dt_control_log(_("%d/%d exported to `%s%s'"), num, total, trunc != filename ? ".." : "", trunc);
  return 0;
}
Esempio n. 9
0
int
store (dt_imageio_module_data_t *sdata, const int imgid, dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata,
       const int num, const int total, const gboolean high_quality)
{
  dt_imageio_gallery_t *d = (dt_imageio_gallery_t *)sdata;

  char filename[DT_MAX_PATH_LEN]= {0};
  char dirname[DT_MAX_PATH_LEN]= {0};
  dt_image_full_path(imgid, dirname, DT_MAX_PATH_LEN);
  // we're potentially called in parallel. have sequence number synchronized:
  dt_pthread_mutex_lock(&darktable.plugin_threadsafe);
  {

    char tmp_dir[DT_MAX_PATH_LEN];
    dt_variables_expand(d->vp, d->filename, TRUE);
    g_strlcpy(tmp_dir, dt_variables_get_result(d->vp), DT_MAX_PATH_LEN);

    // if filenamepattern is a directory just let att ${FILE_NAME} as default..
    if ( g_file_test(tmp_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) || ((d->filename+strlen(d->filename)-1)[0]=='/' || (d->filename+strlen(d->filename)-1)[0]=='\\') )
      snprintf (d->filename+strlen(d->filename), DT_MAX_PATH_LEN-strlen(d->filename), "/$(FILE_NAME)");

    // avoid braindead export which is bound to overwrite at random:
    if(total > 1 && !g_strrstr(d->filename, "$"))
    {
      snprintf(d->filename+strlen(d->filename), DT_MAX_PATH_LEN-strlen(d->filename), "_$(SEQUENCE)");
    }

    gchar* fixed_path = dt_util_fix_path(d->filename);
    g_strlcpy(d->filename, fixed_path, DT_MAX_PATH_LEN);
    g_free(fixed_path);

    d->vp->filename = dirname;
    d->vp->jobcode = "export";
    d->vp->imgid = imgid;
    d->vp->sequence = num;
    dt_variables_expand(d->vp, d->filename, TRUE);
    g_strlcpy(filename, dt_variables_get_result(d->vp), DT_MAX_PATH_LEN);
    g_strlcpy(dirname, filename, DT_MAX_PATH_LEN);

    const char *ext = format->extension(fdata);
    char *c = dirname + strlen(dirname);
    for(; c>dirname && *c != '/'; c--);
    if(*c == '/') *c = '\0';
    if(g_mkdir_with_parents(dirname, 0755))
    {
      fprintf(stderr, "[imageio_storage_gallery] could not create directory: `%s'!\n", dirname);
      dt_control_log(_("could not create directory `%s'!"), dirname);
      dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);
      return 1;
    }

    // store away dir.
    snprintf(d->cached_dirname, DT_MAX_PATH_LEN, "%s", dirname);

    c = filename + strlen(filename);
    for(; c>filename && *c != '.' && *c != '/' ; c--);
    if(c <= filename || *c=='/') c = filename + strlen(filename);

    sprintf(c,".%s",ext);

    // save image to list, in order:
    pair_t *pair = malloc(sizeof(pair_t));

    char *title = NULL, *description = NULL, *tags = NULL;
    GList *res;

    res = dt_metadata_get(imgid, "Xmp.dc.title", NULL);
    if(res)
    {
      title = res->data;
      g_list_free(res);
    }

    res = dt_metadata_get(imgid, "Xmp.dc.description", NULL);
    if(res)
    {
      description = res->data;
      g_list_free(res);
    }

    unsigned int count = 0;
    res = dt_metadata_get(imgid, "Xmp.dc.subject", &count);
    if(res)
    {
      // don't show the internal tags (darktable|...)
      res = g_list_first(res);
      GList *iter = res;
      while(iter)
      {
        GList *next = g_list_next(iter);
        if(g_str_has_prefix(iter->data, "darktable|"))
        {
          g_free(iter->data);
          res = g_list_delete_link(res, iter);
          count--;
        }
        iter = next;
      }
      tags = dt_util_glist_to_str(", ", res, count);
    }

    char relfilename[256], relthumbfilename[256];
    c = filename + strlen(filename);
    for(; c>filename && *c != '/' ; c--);
    if(*c == '/') c++;
    if(c <= filename) c = filename;
    snprintf(relfilename, 256, "%s", c);
    snprintf(relthumbfilename, 256, "%s", relfilename);
    c = relthumbfilename + strlen(relthumbfilename);
    for(; c>relthumbfilename && *c != '.'; c--);
    if(c <= relthumbfilename) c = relthumbfilename + strlen(relthumbfilename);
    sprintf(c, "-thumb.%s", ext);

    char subfilename[DT_MAX_PATH_LEN], relsubfilename[256];
    snprintf(subfilename, DT_MAX_PATH_LEN, "%s", d->cached_dirname);
    char* sc = subfilename + strlen(subfilename);
    sprintf(sc, "/img_%d.html", num);
    sprintf(relsubfilename, "img_%d.html", num);

    snprintf(pair->line, 4096,
             "\n"
             "      <div><a class=\"dia\" rel=\"lightbox[viewer]\" title=\"%s - %s\" href=\"%s\"><span></span><img src=\"%s\" alt=\"img%d\" class=\"img\"/></a>\n"
             "      <h1>%s</h1>\n"
             "      %s<br/><span class=\"tags\">%s</span></div>\n", title?title:relfilename, description?description:"&nbsp;", relfilename, relthumbfilename, num, title?title:"&nbsp;", description?description:"&nbsp;", tags?tags:"&nbsp;");

    char next[256];
    sprintf(next, "img_%d.html", (num)%total+1);

    char prev[256];
    sprintf(prev, "img_%d.html", (num==1)?total:num-1);

    /* Becomes unecessary with the Lightbox image viewer overlay

        FILE* subfile = fopen(subfilename, "wb");
        fprintf(subfile,
              "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
              "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
              "  <head>\n"
              "    <meta http-equiv=\"Content-type\" content=\"text/html;charset=UTF-8\" />\n"
              "    <link rel=\"shortcut icon\" href=\"style/favicon.ico\" />\n"
              "    <link rel=\"stylesheet\" href=\"style/style.css\" type=\"text/css\" />\n"
              "    <title>%s</title>\n"
              "  </head>\n"
              "  <body>\n"
              "    <div class=\"title\"><a href=\"index.html\">%s</a></div>\n"
              "    <div class=\"page\">\n"
              "      <div style=\"width: 692px; max-width: 692px; height: 10px;\">\n"
              "        <a style=\"float: left;\" href=\"%s\"><h1>prev</h1></a>\n"
              "        <a style=\"float: right;\"href=\"%s\"><h1>next</h1></a>\n"
              "      </div>\n"
              "      <a href=\"%s\"><img src=\"%s\" width=\"692\" class=\"img\"/></a>\n"
              "      %s<br/><span class=\"tags\">%s</span></div>\n"
              "      <p style=\"clear:both;\"></p>\n"
              "    </div>\n"
              "    <div class=\"footer\">\n"
              "      created with darktable "PACKAGE_VERSION"\n"
              "    </div>\n"
              "  </body>\n"
              "</html>\n",
              relfilename, title?title:relfilename, prev, next, relfilename, relfilename, description?description:"&nbsp;", tags?tags:"&nbsp;");
        fclose(subfile);
    */
    pair->pos = num;
    g_free(title);
    g_free(description);
    g_free(tags);
    d->l = g_list_insert_sorted(d->l, pair, (GCompareFunc)sort_pos);
  } // end of critical block
  dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);

  /* export image to file */
  if(dt_imageio_export(imgid, filename, format, fdata, high_quality) != 0)
  {
    fprintf(stderr, "[imageio_storage_gallery] could not export to file: `%s'!\n", filename);
    dt_control_log(_("could not export to file `%s'!"), filename);
    return 1;
  }

  /* also export thumbnail: */
  // write with reduced resolution:
  const int max_width  = fdata->max_width;
  const int max_height = fdata->max_height;
  fdata->max_width  = 200;
  fdata->max_height = 200;
  // alter filename with -thumb:
  char *c = filename + strlen(filename);
  for(; c>filename && *c != '.' && *c != '/' ; c--);
  if(c <= filename || *c=='/') c = filename + strlen(filename);
  const char *ext = format->extension(fdata);
  sprintf(c,"-thumb.%s",ext);
  if(dt_imageio_export(imgid, filename, format, fdata, FALSE) != 0)
  {
    fprintf(stderr, "[imageio_storage_gallery] could not export to file: `%s'!\n", filename);
    dt_control_log(_("could not export to file `%s'!"), filename);
    return 1;
  }
  // restore for next image:
  fdata->max_width = max_width;
  fdata->max_height = max_height;

  printf("[export_job] exported to `%s'\n", filename);
  char *trunc = filename + strlen(filename) - 32;
  if(trunc < filename) trunc = filename;
  dt_control_log(_("%d/%d exported to `%s%s'"), num, total, trunc != filename ? ".." : "", trunc);
  return 0;
}
Esempio n. 10
0
int
store (dt_imageio_module_data_t *sdata, const int imgid, dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata,
       const int num, const int total, const gboolean high_quality)
{
  dt_imageio_disk_t *d = (dt_imageio_disk_t *)sdata;

  char filename[DT_MAX_PATH_LEN]= {0};
  char dirname[DT_MAX_PATH_LEN]= {0};
  dt_image_full_path(imgid, dirname, DT_MAX_PATH_LEN);
  int fail = 0;
  // we're potentially called in parallel. have sequence number synchronized:
  dt_pthread_mutex_lock(&darktable.plugin_threadsafe);
  {

    // if filenamepattern is a directory just let att ${FILE_NAME} as default..
    if ( g_file_test(d->filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) || ((d->filename+strlen(d->filename))[0]=='/' || (d->filename+strlen(d->filename))[0]=='\\') )
      snprintf (d->filename+strlen(d->filename), DT_MAX_PATH_LEN-strlen(d->filename), "$(FILE_NAME)");

    // avoid braindead export which is bound to overwrite at random:
    if(total > 1 && !g_strrstr(d->filename, "$"))
    {
      snprintf(d->filename+strlen(d->filename), DT_MAX_PATH_LEN-strlen(d->filename), "_$(SEQUENCE)");
    }

    gchar* fixed_path = dt_util_fix_path(d->filename);
    g_strlcpy(d->filename, fixed_path, DT_MAX_PATH_LEN);
    g_free(fixed_path);

    d->vp->filename = dirname;
    d->vp->jobcode = "export";
    d->vp->imgid = imgid;
    d->vp->sequence = num;
    dt_variables_expand(d->vp, d->filename, TRUE);
    g_strlcpy(filename, dt_variables_get_result(d->vp), DT_MAX_PATH_LEN);
    g_strlcpy(dirname, filename, DT_MAX_PATH_LEN);

    const char *ext = format->extension(fdata);
    char *c = dirname + strlen(dirname);
    for(; c>dirname && *c != '/'; c--);
    if(*c == '/')
    {
      if(c > dirname) // /.../.../foo
        c[0] = '\0';
      else // /foo
        c[1] = '\0';
    }
    else if(c == dirname) // foo
    {
      c[0] = '.'; c[1] = '\0';
    }

    if(g_mkdir_with_parents(dirname, 0755))
    {
      fprintf(stderr, "[imageio_storage_disk] could not create directory: `%s'!\n", dirname);
      dt_control_log(_("could not create directory `%s'!"), dirname);
      fail = 1;
      goto failed;
    }
    if(g_access(dirname, W_OK) != 0)
    {
      fprintf(stderr, "[imageio_storage_disk] could not write to directory: `%s'!\n", dirname);
      dt_control_log(_("could not write to directory `%s'!"), dirname);
      fail = 1;
      goto failed;
    }

    c = filename + strlen(filename);
    // remove everything after the last '.'. this destroys any file name with dots in it since $(FILE_NAME) already comes without the original extension.
//     for(; c>filename && *c != '.' && *c != '/' ; c--);
//     if(c <= filename || *c=='/') c = filename + strlen(filename);

    sprintf(c,".%s",ext);

    /* prevent overwrite of files */
    int seq=1;
failed:
    if (!fail && g_file_test (filename,G_FILE_TEST_EXISTS))
    {
      do
      {
        sprintf(c,"_%.2d.%s",seq,ext);
        seq++;
      }
      while (g_file_test (filename,G_FILE_TEST_EXISTS));
    }

  } // end of critical block
  dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);
  if(fail) return 1;

  /* export image to file */
  if(dt_imageio_export(imgid, filename, format, fdata, high_quality) != 0)
  {
    fprintf(stderr, "[imageio_storage_disk] could not export to file: `%s'!\n", filename);
    dt_control_log(_("could not export to file `%s'!"), filename);
    return 1;
  }

  /* now write xmp into that container, if possible */
  if((format->flags() & FORMAT_FLAGS_SUPPORT_XMP) && dt_exif_xmp_attach(imgid, filename) != 0)
  {
    fprintf(stderr, "[imageio_storage_disk] could not attach xmp data to file: `%s'!\n", filename);
    // don't report that one to gui, as some formats (pfm, ppm, exr) just don't support
    // writing xmp via exiv2, so it might not be to worry.
    return 1;
  }

  printf("[export_job] exported to `%s'\n", filename);
  char *trunc = filename + strlen(filename) - 32;
  if(trunc < filename) trunc = filename;
  dt_control_log(_("%d/%d exported to `%s%s'"), num, total, trunc != filename ? ".." : "", trunc);
  return 0;
}
Esempio n. 11
0
int store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *sdata, const int imgid,
          dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total,
          const gboolean high_quality, const gboolean upscale)
{
  dt_imageio_gallery_t *d = (dt_imageio_gallery_t *)sdata;

  char filename[PATH_MAX] = { 0 };
  char dirname[PATH_MAX] = { 0 };
  gboolean from_cache = FALSE;
  dt_image_full_path(imgid, dirname, sizeof(dirname), &from_cache);
  // we're potentially called in parallel. have sequence number synchronized:
  dt_pthread_mutex_lock(&darktable.plugin_threadsafe);
  {

    char tmp_dir[PATH_MAX] = { 0 };

    d->vp->filename = dirname;
    d->vp->jobcode = "export";
    d->vp->imgid = imgid;
    d->vp->sequence = num;
    dt_variables_expand(d->vp, d->filename, TRUE);

    gchar *result_tmp_dir = dt_variables_get_result(d->vp);
    g_strlcpy(tmp_dir, result_tmp_dir, sizeof(tmp_dir));
    g_free(result_tmp_dir);

    // if filenamepattern is a directory just let att ${FILE_NAME} as default..
    if(g_file_test(tmp_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)
       || ((d->filename + strlen(d->filename) - 1)[0] == '/'
           || (d->filename + strlen(d->filename) - 1)[0] == '\\'))
      snprintf(d->filename + strlen(d->filename), sizeof(d->filename) - strlen(d->filename), "/$(FILE_NAME)");

    // avoid braindead export which is bound to overwrite at random:
    if(total > 1 && !g_strrstr(d->filename, "$"))
    {
      snprintf(d->filename + strlen(d->filename), sizeof(d->filename) - strlen(d->filename), "_$(SEQUENCE)");
    }

    gchar *fixed_path = dt_util_fix_path(d->filename);
    g_strlcpy(d->filename, fixed_path, sizeof(d->filename));
    g_free(fixed_path);

    dt_variables_expand(d->vp, d->filename, TRUE);

    gchar *result_filename = dt_variables_get_result(d->vp);
    g_strlcpy(filename, result_filename, sizeof(filename));
    g_free(result_filename);

    g_strlcpy(dirname, filename, sizeof(dirname));

    const char *ext = format->extension(fdata);
    char *c = dirname + strlen(dirname);
    for(; c > dirname && *c != '/'; c--)
      ;
    if(*c == '/') *c = '\0';
    if(g_mkdir_with_parents(dirname, 0755))
    {
      fprintf(stderr, "[imageio_storage_gallery] could not create directory: `%s'!\n", dirname);
      dt_control_log(_("could not create directory `%s'!"), dirname);
      dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);
      return 1;
    }

    // store away dir.
    snprintf(d->cached_dirname, sizeof(d->cached_dirname), "%s", dirname);

    c = filename + strlen(filename);
    for(; c > filename && *c != '.' && *c != '/'; c--)
      ;
    if(c <= filename || *c == '/') c = filename + strlen(filename);

    sprintf(c, ".%s", ext);

    // save image to list, in order:
    pair_t *pair = malloc(sizeof(pair_t));

    char *title = NULL, *description = NULL;
    GList *res_title, *res_desc;

    res_title = dt_metadata_get(imgid, "Xmp.dc.title", NULL);
    if(res_title)
    {
      title = res_title->data;
    }

    res_desc = dt_metadata_get(imgid, "Xmp.dc.description", NULL);
    if(res_desc)
    {
      description = res_desc->data;
    }

    char relfilename[PATH_MAX] = { 0 }, relthumbfilename[PATH_MAX] = { 0 };
    c = filename + strlen(filename);
    for(; c > filename && *c != '/'; c--)
      ;
    if(*c == '/') c++;
    if(c <= filename) c = filename;
    snprintf(relfilename, sizeof(relfilename), "%s", c);
    snprintf(relthumbfilename, sizeof(relthumbfilename), "%s", relfilename);
    c = relthumbfilename + strlen(relthumbfilename);
    for(; c > relthumbfilename && *c != '.'; c--)
      ;
    if(c <= relthumbfilename) c = relthumbfilename + strlen(relthumbfilename);
    sprintf(c, "-thumb.%s", ext);

    char subfilename[PATH_MAX] = { 0 }, relsubfilename[PATH_MAX] = { 0 };
    snprintf(subfilename, sizeof(subfilename), "%s", d->cached_dirname);
    char *sc = subfilename + strlen(subfilename);
    sprintf(sc, "/img_%d.html", num);
    snprintf(relsubfilename, sizeof(relsubfilename), "img_%d.html", num);

    snprintf(pair->line, sizeof(pair->line),
             "\n"
             "      <div><a class=\"dia\" rel=\"lightbox[viewer]\" title=\"%s - %s\" "
             "href=\"%s\"><span></span><img src=\"%s\" alt=\"img%d\" class=\"img\"/></a>\n"
             "      <h1>%s</h1>\n"
             "      %s</div>\n",
             title ? title : relfilename, description ? description : "&nbsp;", relfilename, relthumbfilename,
             num, title ? title : "&nbsp;", description ? description : "&nbsp;");

    char next[PATH_MAX] = { 0 };
    snprintf(next, sizeof(next), "img_%d.html", (num) % total + 1);

    char prev[PATH_MAX] = { 0 };
    snprintf(prev, sizeof(prev), "img_%d.html", (num == 1) ? total : num - 1);

    pair->pos = num;
    if(res_title) g_list_free_full(res_title, &g_free);
    if(res_desc) g_list_free_full(res_desc, &g_free);
    d->l = g_list_insert_sorted(d->l, pair, (GCompareFunc)sort_pos);
  } // end of critical block
  dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);

  /* export image to file */
  if(dt_imageio_export(imgid, filename, format, fdata, high_quality, upscale, FALSE, self, sdata, num, total) != 0)
  {
    fprintf(stderr, "[imageio_storage_gallery] could not export to file: `%s'!\n", filename);
    dt_control_log(_("could not export to file `%s'!"), filename);
    return 1;
  }

  /* also export thumbnail: */
  // write with reduced resolution:
  const int max_width = fdata->max_width;
  const int max_height = fdata->max_height;
  fdata->max_width = 200;
  fdata->max_height = 200;
  // alter filename with -thumb:
  char *c = filename + strlen(filename);
  for(; c > filename && *c != '.' && *c != '/'; c--)
    ;
  if(c <= filename || *c == '/') c = filename + strlen(filename);
  const char *ext = format->extension(fdata);
  sprintf(c, "-thumb.%s", ext);
  if(dt_imageio_export(imgid, filename, format, fdata, FALSE, TRUE, FALSE, self, sdata, num, total) != 0)
  {
    fprintf(stderr, "[imageio_storage_gallery] could not export to file: `%s'!\n", filename);
    dt_control_log(_("could not export to file `%s'!"), filename);
    return 1;
  }
  // restore for next image:
  fdata->max_width = max_width;
  fdata->max_height = max_height;

  printf("[export_job] exported to `%s'\n", filename);
  char *trunc = filename + strlen(filename) - 32;
  if(trunc < filename) trunc = filename;
  dt_control_log(ngettext("%d/%d exported to `%s%s'", "%d/%d exported to `%s%s'", num),
                 num, total, trunc != filename ? ".." : "", trunc);
  return 0;
}
Esempio n. 12
0
void _capture_view_set_jobcode(const dt_view_t *view, const char *name)
{
  g_assert( view != NULL );
  dt_capture_t *cv=(dt_capture_t *)view->data;

  /* take care of previous capture filmroll */
  if( cv->film )
  {
    if( dt_film_is_empty(cv->film->id) )
      dt_film_remove(cv->film->id );
    else
      dt_film_cleanup( cv->film );
  }

  /* lets initialize a new filmroll for the capture... */
  cv->film = (dt_film_t*)malloc(sizeof(dt_film_t));
  if(!cv->film) return;
  dt_film_init(cv->film);

  int current_filmroll = dt_conf_get_int("plugins/capture/current_filmroll");
  if(current_filmroll >= 0)
  {
    /* open existing filmroll and import captured images into this roll */
    cv->film->id = current_filmroll;
    if (dt_film_open2 (cv->film) !=0)
    {
      /* failed to open the current filmroll, let's reset and create a new one */
      dt_conf_set_int ("plugins/capture/current_filmroll",-1);
    }
    else
      cv->path = g_strdup(cv->film->dirname);

  }

  if (dt_conf_get_int ("plugins/capture/current_filmroll") == -1)
  {
    if(cv->jobcode)
      g_free(cv->jobcode);
    cv->jobcode = g_strdup(name);

    // Setup variables jobcode...
    cv->vp->jobcode = cv->jobcode;

    /* reset session sequence number */
    dt_variables_reset_sequence (cv->vp);

    // Construct the directory for filmroll...
    gchar* path = g_build_path(G_DIR_SEPARATOR_S,cv->basedirectory,cv->subdirectory, (char *)NULL);
    cv->path = dt_util_fix_path(path);
    g_free(path);

    dt_variables_expand( cv->vp, cv->path, FALSE );
    sprintf(cv->film->dirname,"%s",dt_variables_get_result(cv->vp));

    // Create recursive directories, abort if no access
    if( g_mkdir_with_parents(cv->film->dirname,0755) == -1 )
    {
      dt_control_log(_("failed to create session path %s."), cv->film->dirname);
      if(cv->film)
      {
        free( cv->film );
        cv->film = NULL;
      }
      return;
    }

    if(dt_film_new(cv->film,cv->film->dirname) > 0)
    {
      // Switch to new filmroll
      dt_film_open(cv->film->id);

      /* store current filmroll */
      dt_conf_set_int("plugins/capture/current_filmroll",cv->film->id);
    }

    dt_control_log(_("new session initiated '%s'"),cv->jobcode,cv->film->id);
  }


}
Esempio n. 13
0
int store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *sdata, const int imgid,
          dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total,
          const gboolean high_quality, const gboolean upscale)
{
  dt_imageio_latex_t *d = (dt_imageio_latex_t *)sdata;

  char filename[PATH_MAX] = { 0 };
  char dirname[PATH_MAX] = { 0 };
  gboolean from_cache = FALSE;
  dt_image_full_path(imgid, dirname, sizeof(dirname), &from_cache);
  // we're potentially called in parallel. have sequence number synchronized:
  dt_pthread_mutex_lock(&darktable.plugin_threadsafe);
  {

    // if filenamepattern is a directory just add ${FILE_NAME} as default..
    if(g_file_test(d->filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)
       || ((d->filename + strlen(d->filename))[0] == '/' || (d->filename + strlen(d->filename))[0] == '\\'))
      snprintf(d->filename + strlen(d->filename), sizeof(d->filename) - strlen(d->filename), "$(FILE_NAME)");

    // avoid braindead export which is bound to overwrite at random:
    if(total > 1 && !g_strrstr(d->filename, "$"))
    {
      snprintf(d->filename + strlen(d->filename), sizeof(d->filename) - strlen(d->filename), "_$(SEQUENCE)");
    }

    gchar *fixed_path = dt_util_fix_path(d->filename);
    g_strlcpy(d->filename, fixed_path, sizeof(d->filename));
    g_free(fixed_path);

    d->vp->filename = dirname;
    d->vp->jobcode = "export";
    d->vp->imgid = imgid;
    d->vp->sequence = num;
    dt_variables_expand(d->vp, d->filename, TRUE);

    gchar *result_filename = dt_variables_get_result(d->vp);
    g_strlcpy(filename, result_filename, sizeof(filename));
    g_free(result_filename);

    g_strlcpy(dirname, filename, sizeof(dirname));

    const char *ext = format->extension(fdata);
    char *c = dirname + strlen(dirname);
    for(; c > dirname && *c != '/'; c--)
      ;
    if(*c == '/') *c = '\0';
    if(g_mkdir_with_parents(dirname, 0755))
    {
      fprintf(stderr, "[imageio_storage_latex] could not create directory: `%s'!\n", dirname);
      dt_control_log(_("could not create directory `%s'!"), dirname);
      dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);
      return 1;
    }

    // store away dir.
    snprintf(d->cached_dirname, sizeof(d->cached_dirname), "%s", dirname);

    c = filename + strlen(filename);
    //     for(; c>filename && *c != '.' && *c != '/' ; c--);
    //     if(c <= filename || *c=='/') c = filename + strlen(filename);

    sprintf(c, ".%s", ext);

    // save image to list, in order:
    pair_t *pair = malloc(sizeof(pair_t));


#if 0 // let's see if we actually want titles and such to be exported:
    char *title = NULL, *description = NULL, *tags = NULL;
    GList *res_title, *res_desc, *res_subj;

    res_title = dt_metadata_get(imgid, "Xmp.dc.title", NULL);
    if(res_title)
    {
      title = res_title->data;
    }

    res_desc = dt_metadata_get(imgid, "Xmp.dc.description", NULL);
    if(res_desc)
    {
      description = res_desc->data;
    }

    res_subj = dt_metadata_get(imgid, "Xmp.dc.subject", NULL);
    if(res_subj)
    {
      // don't show the internal tags (darktable|...)
      res_subj = g_list_first(res_subj);
      GList *iter = res_subj;
      while(iter)
      {
        GList *next = g_list_next(iter);
        if(g_str_has_prefix(iter->data, "darktable|"))
        {
          g_free(iter->data);
          res_subj = g_list_delete_link(res_subj, iter);
        }
        iter = next;
      }
      tags = dt_util_glist_to_str(", ", res_subj);
      g_list_free_full(res_subj, g_free);
    }
#endif

    char relfilename[PATH_MAX] = { 0 };
    c = filename + strlen(filename);
    for(; c > filename && *c != '/'; c--)
      ;
    if(*c == '/') c++;
    if(c <= filename) c = filename;
    snprintf(relfilename, sizeof(relfilename), "%s", c);

    snprintf(pair->line, sizeof(pair->line),
             "\\begin{minipage}{\\imgwidth}%%\n"
             "\\drawtrimcorners%%\n"
             "\\vskip0pt plus 1filll\n"
             "\\begin{minipage}{\\imgwidth}%%\n"
             " \\hfil\\includegraphics[width=\\imgwidth,height=\\imgheight,keepaspectratio]{%s}\\hfil\n"
             "  %% put text under image here\n"
             "\\end{minipage}\n"
             "\\end{minipage}\n"
             "\\newpage\n\n",
             relfilename);

    pair->pos = num;
    // g_list_free_full(res_title, &g_free);
    // g_list_free_full(res_desc, &g_free);
    // g_list_free_full(res_subj, &g_free);
    // g_free(tags);
    d->l = g_list_insert_sorted(d->l, pair, (GCompareFunc)sort_pos);
  } // end of critical block
  dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);

  /* export image to file */
  dt_imageio_export(imgid, filename, format, fdata, high_quality, upscale, FALSE, self, sdata, num, total);

  printf("[export_job] exported to `%s'\n", filename);
  char *trunc = filename + strlen(filename) - 32;
  if(trunc < filename) trunc = filename;
  dt_control_log(ngettext("%d/%d exported to `%s%s'", "%d/%d exported to `%s%s'", num),
                 num, total, trunc != filename ? ".." : "", trunc);
  return 0;
}
Esempio n. 14
0
int32_t dt_camera_import_job_run(dt_job_t *job)
{
  dt_camera_import_t *t = (dt_camera_import_t *)job->param;
  dt_control_log(_("starting to import images from camera"));

  // Setup a new filmroll to import images to....
  t->film=(dt_film_t*)g_malloc(sizeof(dt_film_t));

  dt_film_init(t->film);

  gchar* fixed_path = dt_util_fix_path(t->path);
  g_free(t->path);
  t->path = fixed_path;
  dt_variables_expand( t->vp, t->path, FALSE );
  sprintf(t->film->dirname,"%s",dt_variables_get_result(t->vp));

  dt_pthread_mutex_lock(&t->film->images_mutex);
  t->film->ref++;
  dt_pthread_mutex_unlock(&t->film->images_mutex);

  // Create recursive directories, abort if no access
  if( g_mkdir_with_parents(t->film->dirname,0755) == -1 )
  {
    dt_control_log(_("failed to create import path `%s', import aborted."), t->film->dirname);
    return 1;
  }

  // Import path is ok, lets actually create the filmroll in database..
  if(dt_film_new(t->film,t->film->dirname) > 0)
  {
    int total = g_list_length( t->images );
    char message[512]= {0};
    sprintf(message, ngettext ("importing %d image from camera", "importing %d images from camera", total), total );
    t->bgj = dt_control_backgroundjobs_create(darktable.control, 0, message);

    // Switch to new filmroll
    dt_film_open(t->film->id);
    dt_ctl_switch_mode_to(DT_LIBRARY);

    // register listener
    dt_camctl_listener_t listener= {0};
    listener.data=t;
    listener.image_downloaded=_camera_image_downloaded;
    listener.request_image_path=_camera_import_request_image_path;
    listener.request_image_filename=_camera_import_request_image_filename;

    //  start download of images
    dt_camctl_register_listener(darktable.camctl,&listener);
    dt_camctl_import(darktable.camctl,t->camera,t->images,dt_conf_get_bool("plugins/capture/camera/import/delete_originals"));
    dt_camctl_unregister_listener(darktable.camctl,&listener);
    dt_control_backgroundjobs_destroy(darktable.control, t->bgj);
    dt_variables_params_destroy(t->vp);
  }
  else
    dt_control_log(_("failed to create filmroll for camera import, import aborted."));

  dt_pthread_mutex_lock(&t->film->images_mutex);
  t->film->ref--;
  dt_pthread_mutex_unlock(&t->film->images_mutex);
  return 0;
}
Esempio n. 15
0
int store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *sdata, const int imgid,
          dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total,
          const gboolean high_quality, const gboolean upscale)
{
  dt_imageio_disk_t *d = (dt_imageio_disk_t *)sdata;

  char filename[PATH_MAX] = { 0 };
  char dirname[PATH_MAX] = { 0 };
  gboolean from_cache = FALSE;
  dt_image_full_path(imgid, dirname, sizeof(dirname), &from_cache);
  int fail = 0;
  // we're potentially called in parallel. have sequence number synchronized:
  dt_pthread_mutex_lock(&darktable.plugin_threadsafe);
  {

    // if filenamepattern is a directory just let att ${FILE_NAME} as default..
    if(g_file_test(d->filename, G_FILE_TEST_IS_DIR)
       || ((d->filename + strlen(d->filename))[0] == '/' || (d->filename + strlen(d->filename))[0] == '\\'))
      snprintf(d->filename + strlen(d->filename), sizeof(d->filename) - strlen(d->filename), "$(FILE_NAME)");

    // avoid braindead export which is bound to overwrite at random:
    if(total > 1 && !g_strrstr(d->filename, "$"))
    {
      snprintf(d->filename + strlen(d->filename), sizeof(d->filename) - strlen(d->filename), "_$(SEQUENCE)");
    }

    gchar *fixed_path = dt_util_fix_path(d->filename);
    g_strlcpy(d->filename, fixed_path, sizeof(d->filename));
    g_free(fixed_path);

    d->vp->filename = dirname;
    d->vp->jobcode = "export";
    d->vp->imgid = imgid;
    d->vp->sequence = num;
    dt_variables_expand(d->vp, d->filename, TRUE);

    gchar *result_filename = dt_variables_get_result(d->vp);
    g_strlcpy(filename, result_filename, sizeof(filename));
    g_free(result_filename);

    g_strlcpy(dirname, filename, sizeof(dirname));

    const char *ext = format->extension(fdata);
    char *c = dirname + strlen(dirname);
    for(; c > dirname && *c != '/'; c--)
      ;
    if(*c == '/')
    {
      if(c > dirname) // /.../.../foo
        c[0] = '\0';
      else // /foo
        c[1] = '\0';
    }
    else if(c == dirname) // foo
    {
      c[0] = '.';
      c[1] = '\0';
    }

    if(g_mkdir_with_parents(dirname, 0755))
    {
      fprintf(stderr, "[imageio_storage_disk] could not create directory: `%s'!\n", dirname);
      dt_control_log(_("could not create directory `%s'!"), dirname);
      fail = 1;
      goto failed;
    }
    if(g_access(dirname, W_OK | X_OK) != 0)
    {
      fprintf(stderr, "[imageio_storage_disk] could not write to directory: `%s'!\n", dirname);
      dt_control_log(_("could not write to directory `%s'!"), dirname);
      fail = 1;
      goto failed;
    }

    c = filename + strlen(filename);
    // remove everything after the last '.'. this destroys any file name with dots in it since $(FILE_NAME)
    // already comes without the original extension.
    //     for(; c>filename && *c != '.' && *c != '/' ; c--);
    //     if(c <= filename || *c=='/') c = filename + strlen(filename);

    sprintf(c, ".%s", ext);

  /* prevent overwrite of files */
  failed:
    if(!d->overwrite)
    {
      int seq = 1;
      if(!fail && g_file_test(filename, G_FILE_TEST_EXISTS))
      {
        do
        {
          sprintf(c, "_%.2d.%s", seq, ext);
          seq++;
        } while(g_file_test(filename, G_FILE_TEST_EXISTS));
      }
    }
  } // end of critical block
  dt_pthread_mutex_unlock(&darktable.plugin_threadsafe);
  if(fail) return 1;

  /* export image to file */
  if(dt_imageio_export(imgid, filename, format, fdata, high_quality, upscale, TRUE, self, sdata, num, total) != 0)
  {
    fprintf(stderr, "[imageio_storage_disk] could not export to file: `%s'!\n", filename);
    dt_control_log(_("could not export to file `%s'!"), filename);
    return 1;
  }

  printf("[export_job] exported to `%s'\n", filename);
  char *trunc = filename + strlen(filename) - 32;
  if(trunc < filename) trunc = filename;
  dt_control_log(ngettext("%d/%d exported to `%s%s'", "%d/%d exported to `%s%s'", num),
                 num, total, trunc != filename ? ".." : "", trunc);
  return 0;
}