screenshot-filename-builder.c 7.19 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
Dominique Leuenberger's avatar
Dominique Leuenberger committed
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>

Bastien Nocera's avatar
Bastien Nocera committed
28
#include "screenshot-filename-builder.h"
29
#include "screenshot-config.h"
Bastien Nocera's avatar
Bastien Nocera committed
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
  int iteration;
  TestType type;
} AsyncExistenceJob;

/* Taken from gnome-vfs-utils.c */
static char *
expand_initial_tilde (const char *path)
{
51
52
  g_autofree gchar *user_name = NULL;
  char *slash_after_user_name;
53
54
55
  struct passwd *passwd_file_entry;

  if (path[1] == '/' || path[1] == '\0') {
56
    return g_build_filename (g_get_home_dir (), &path[1], NULL);
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);
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
}

static gchar *
sanitize_save_directory (const gchar *save_dir)
{
Bastien Nocera's avatar
Bastien Nocera committed
92
93
94
  if (save_dir == NULL)
    return NULL;

95
  if (save_dir[0] == '~')
96
    return expand_initial_tilde (save_dir);
97

98
99
100
101
  if (strstr (save_dir, "://") != NULL)
    {
      g_autoptr(GFile) file = g_file_new_for_uri (save_dir);
      return g_file_get_path (file);
102
    }
103

104
  return g_strdup (save_dir);
105
106
107
}

static char *
108
build_path (AsyncExistenceJob *job)
109
{
110
  g_autofree gchar *file_name = NULL, *origin = NULL;
111
  const gchar *base_path, *file_type;
112

113
  base_path = job->base_paths[job->type];
114
  file_type = screenshot_config->file_type;
115

116
117
  if (base_path == NULL ||
      base_path[0] == '\0')
118
119
    return NULL;

120
121
  if (job->screenshot_origin == NULL)
    {
122
      g_autoptr(GDateTime) d = g_date_time_new_now_local ();
123
      origin = g_date_time_format (d, "%Y-%m-%d %H-%M-%S");
124
125
126
    }
  else
    origin = g_strdup (job->screenshot_origin);
127
128
129

  if (job->iteration == 0)
    {
130
131
132
133
134
      /* 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").
       */
135
      file_name = g_strdup_printf (_("Screenshot from %s.%s"), origin, file_type);
136
137
138
    }
  else
    {
139
140
141
142
143
144
      /* 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").
       */
145
      file_name = g_strdup_printf (_("Screenshot from %s - %d.%s"), origin, job->iteration, file_type);
146
147
    }

148
  return g_build_filename (base_path, file_name, NULL);
149
150
151
152
153
154
155
}

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

156
  for (idx = 0; idx < NUM_TESTS; idx++)
157
    g_free (job->base_paths[idx]);
158

159
160
  g_free (job->screenshot_origin);

161
162
163
  g_slice_free (AsyncExistenceJob, job);
}

164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
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;
}

180
181
182
183
184
static void
try_check_file (GTask *task,
                gpointer source_object,
                gpointer data,
                GCancellable *cancellable)
185
186
187
{
  AsyncExistenceJob *job = data;

188
  while (TRUE)
189
    {
190
191
192
193
      g_autoptr(GError) error = NULL;
      g_autoptr(GFile) file = NULL;
      g_autoptr(GFileInfo) info = NULL;
      g_autofree gchar *path = build_path (job);
194

195
196
197
198
199
      if (path == NULL)
        {
          (job->type)++;
          continue;
        }
200

201
202
203
204
205
206
207
208
209
      file = g_file_new_for_path (path);
      info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE,
                                G_FILE_QUERY_INFO_NONE, cancellable, &error);
      if (info != NULL)
        {
          /* file already exists, iterate again */
          (job->iteration)++;
          continue;
        }
210
211
212
213

      /* see the error to check whether the location is not accessible
       * or the file does not exist.
       */
214
      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
215
        {
216
          g_autoptr(GFile) parent = g_file_get_parent (file);
217

218
          /* if the parent directory doesn't exist as well, we'll forget the saved
219
220
           * directory and treat this as a generic error.
           */
221
          if (g_file_query_exists (parent, NULL))
222
            {
223
224
              g_task_return_pointer (task, g_steal_pointer (&path), NULL);
              return;
225
226
227
            }
        }

228
229
230
231
232
233
234
      if (!prepare_next_cycle (job))
        {
          g_task_return_new_error (task,
                                   G_IO_ERROR,
                                   G_IO_ERROR_FAILED,
                                   "%s", "Failed to find a valid place to save");
          return;
235
236
237
238
239
        }
    }
}

void
240
screenshot_build_filename_async (const char *save_dir,
241
                                 const char *screenshot_origin,
242
                                 GAsyncReadyCallback callback,
243
244
245
                                 gpointer user_data)
{
  AsyncExistenceJob *job;
246
  g_autoptr(GTask) task = NULL;
247
248
249

  job = g_slice_new0 (AsyncExistenceJob);

250
251
252
  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 ();
253
  job->iteration = 0;
254
  job->type = TEST_SAVED_DIR;
255

256
257
  job->screenshot_origin = g_strdup (screenshot_origin);

258
259
  task = g_task_new (NULL, NULL, callback, user_data);
  g_task_set_task_data (task, job, (GDestroyNotify) async_existence_job_free);
260

261
  g_task_run_in_thread (task, try_check_file);
262
263
264
265
266
267
}

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