screenshot-filename-builder.c 8.13 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* screenshot-filename-builder.c - Builds a filename suitable for a screenshot
 *
 * Copyright (C) 2008, 2011 Cosimo Cecchi <cosimoc@gnome.org>
 *
 * 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
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
18 19 20
 * USA
 */

21 22
#include "config.h"

23 24 25 26 27
#include <gio/gio.h>
#include <glib/gi18n.h>
#include <pwd.h>
#include <string.h>

28
#include "screenshot-filename-builder.h"
29
#include "screenshot-config.h"
30

31 32
typedef enum
{
33 34
  TEST_SAVED_DIR = 0,
  TEST_DEFAULT,
35
  TEST_FALLBACK,
36
  NUM_TESTS
37 38
} TestType;

39
typedef struct
40
{
41
  char *base_paths[NUM_TESTS];
42
  char *screenshot_origin;
43 44 45 46 47 48 49 50 51 52 53 54
  int iteration;
  TestType type;
} AsyncExistenceJob;

/* Taken from gnome-vfs-utils.c */
static char *
expand_initial_tilde (const char *path)
{
  char *slash_after_user_name, *user_name;
  struct passwd *passwd_file_entry;

  if (path[1] == '/' || path[1] == '\0') {
55
    return g_build_filename (g_get_home_dir (), &path[1], NULL);
56
  }
57

58 59 60 61 62 63 64 65 66
  slash_after_user_name = strchr (&path[1], '/');
  if (slash_after_user_name == NULL) {
    user_name = g_strdup (&path[1]);
  } else {
    user_name = g_strndup (&path[1],
                           slash_after_user_name - &path[1]);
  }
  passwd_file_entry = getpwnam (user_name);
  g_free (user_name);
67

68 69 70
  if (passwd_file_entry == NULL || passwd_file_entry->pw_dir == NULL) {
    return g_strdup (path);
  }
71

72 73 74 75 76
  return g_strconcat (passwd_file_entry->pw_dir,
                      slash_after_user_name,
                      NULL);
}

77 78 79
static gchar *
get_fallback_screenshot_dir (void)
{
80
  return g_strdup (g_get_home_dir ());
81 82
}

83 84 85
static gchar *
get_default_screenshot_dir (void)
{
86
  return g_strdup (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES));
87 88 89 90 91 92 93
}

static gchar *
sanitize_save_directory (const gchar *save_dir)
{
  gchar *retval = g_strdup (save_dir);

Bastien Nocera's avatar
Bastien Nocera committed
94 95 96
  if (save_dir == NULL)
    return NULL;

97 98 99 100 101 102
  if (save_dir[0] == '~')
    {
      char *tmp = expand_initial_tilde (save_dir);
      g_free (retval);
      retval = tmp;
    }
103 104 105 106 107 108 109 110 111
  else if (strstr (save_dir, "://") != NULL)
    {
      GFile *file;

      g_free (retval);
      file = g_file_new_for_uri (save_dir);
      retval = g_file_get_path (file);
      g_object_unref (file);
    }
112 113 114 115 116

  return retval;
}

static char *
117
build_path (AsyncExistenceJob *job)
118
{
119 120
  const gchar *base_path, *file_type;
  char *retval, *file_name, *origin;
121

122
  base_path = job->base_paths[job->type];
123
  file_type = screenshot_config->file_type;
124

125 126
  if (base_path == NULL ||
      base_path[0] == '\0')
127 128
    return NULL;

129 130 131 132 133
  if (job->screenshot_origin == NULL)
    {
      GDateTime *d;

      d = g_date_time_new_now_local ();
134
      origin = g_date_time_format (d, "%Y-%m-%d %H-%M-%S");
135 136 137 138
      g_date_time_unref (d);
    }
  else
    origin = g_strdup (job->screenshot_origin);
139 140 141

  if (job->iteration == 0)
    {
142 143 144 145 146
      /* translators: this is the name of the file that gets made up with the
       * screenshot if the entire screen is taken. The first placeholder is a
       * timestamp (e.g. "2017-05-21 12-24-03"); the second placeholder is the
       * file format (e.g. "png").
       */
147
      file_name = g_strdup_printf (_("Screenshot from %s.%s"), origin, file_type);
148 149 150
    }
  else
    {
151 152 153 154 155 156
      /* translators: this is the name of the file that gets made up with the
       * screenshot if the entire screen is taken and the simpler filename
       * already exists. The first and second placeholders are a timestamp and
       * a counter to make it unique (e.g. "2017-05-21 12-24-03 - 2"); the third
       * placeholder is the file format (e.g. "png").
       */
157
      file_name = g_strdup_printf (_("Screenshot from %s - %d.%s"), origin, job->iteration, file_type);
158 159
    }

160
  retval = g_build_filename (base_path, file_name, NULL);
161
  g_free (file_name);
162
  g_free (origin);
163 164 165 166 167 168 169 170 171

  return retval;
}

static void
async_existence_job_free (AsyncExistenceJob *job)
{
  gint idx;

172
  for (idx = 0; idx < NUM_TESTS; idx++)
173
    g_free (job->base_paths[idx]);
174

175 176
  g_free (job->screenshot_origin);

177 178 179
  g_slice_free (AsyncExistenceJob, job);
}

180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
static gboolean
prepare_next_cycle (AsyncExistenceJob *job)
{
  gboolean res = FALSE;

  if (job->type != (NUM_TESTS - 1))
    {
      (job->type)++;
      job->iteration = 0;

      res = TRUE;
    }

  return res;
}

196 197 198 199 200
static void
try_check_file (GTask *task,
                gpointer source_object,
                gpointer data,
                GCancellable *cancellable)
201 202 203 204 205
{
  AsyncExistenceJob *job = data;
  GFile *file;
  GFileInfo *info;
  GError *error;
206
  char *path, *retval;
207 208 209

retry:
  error = NULL;
210
  path = build_path (job);
211

212
  if (path == NULL)
213 214 215 216 217
    {
      (job->type)++;
      goto retry;
    }

218
  file = g_file_new_for_path (path);
219
  info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE,
220
                            G_FILE_QUERY_INFO_NONE, cancellable, &error);
221 222 223 224 225
  if (info != NULL)
    {
      /* file already exists, iterate again */
      g_object_unref (info);
      g_object_unref (file);
226
      g_free (path);
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248

      (job->iteration)++;

      goto retry;
    }
  else
    {
      /* see the error to check whether the location is not accessible
       * or the file does not exist.
       */
      if (error->code == G_IO_ERROR_NOT_FOUND)
        {
          GFile *parent;

          /* if the parent directory doesn't exist as well, forget the saved
           * directory and treat this as a generic error.
           */

          parent = g_file_get_parent (file);

          if (!g_file_query_exists (parent, NULL))
            {
249 250 251 252 253 254 255
              if (!prepare_next_cycle (job))
                {
                  retval = NULL;

                  g_object_unref (parent);
                  goto out;
                }
256 257 258 259 260 261 262

              g_object_unref (file);
              g_object_unref (parent);
              goto retry;
            }
          else
            {
263
              retval = path;
264 265 266 267 268 269 270 271 272 273

              g_object_unref (parent);
              goto out;
            }
        }
      else
        {
          /* another kind of error, assume this location is not
           * accessible.
           */
274
          g_free (path);
275

276 277
          if (prepare_next_cycle (job))
            {
278 279 280 281
              g_error_free (error);
              g_object_unref (file);
              goto retry;
            }
282 283 284 285 286
          else
            {
              retval = NULL;
              goto out;
            }
287 288 289 290 291 292 293 294
        }
    }

out:
  g_error_free (error);
  g_object_unref (file);

  if (retval == NULL)
295 296 297 298
    g_task_return_new_error (task,
                             G_IO_ERROR,
                             G_IO_ERROR_FAILED,
                             "%s", "Failed to find a valid place to save");
299

300
  g_task_return_pointer (task, retval, NULL);
301 302 303
}

void
304
screenshot_build_filename_async (const char *save_dir,
305
                                 const char *screenshot_origin,
306
                                 GAsyncReadyCallback callback,
307 308 309
                                 gpointer user_data)
{
  AsyncExistenceJob *job;
310
  GTask *task;
311 312 313

  job = g_slice_new0 (AsyncExistenceJob);

314 315 316
  job->base_paths[TEST_SAVED_DIR] = sanitize_save_directory (save_dir);
  job->base_paths[TEST_DEFAULT] = get_default_screenshot_dir ();
  job->base_paths[TEST_FALLBACK] = get_fallback_screenshot_dir ();
317
  job->iteration = 0;
318
  job->type = TEST_SAVED_DIR;
319

320 321
  job->screenshot_origin = g_strdup (screenshot_origin);

322 323
  task = g_task_new (NULL, NULL, callback, user_data);
  g_task_set_task_data (task, job, (GDestroyNotify) async_existence_job_free);
324

325 326
  g_task_run_in_thread (task, try_check_file);
  g_object_unref (task);
327 328 329 330 331 332
}

gchar *
screenshot_build_filename_finish (GAsyncResult *result,
                                  GError **error)
{
333
  return g_task_propagate_pointer (G_TASK (result), error);
334
}