|
66 | 66 | #include <libxml/parser.h>
|
67 | 67 | #include <pwd.h>
|
68 | 68 | #include <stdlib.h>
|
| 69 | +#include <ctype.h> |
69 | 70 | #include <sys/time.h>
|
70 | 71 | #include <time.h>
|
71 | 72 | #include <unistd.h>
|
|
99 | 100 |
|
100 | 101 | #define METADATA_ID_IS_LIST_MASK (1<<31)
|
101 | 102 |
|
| 103 | +#define SORT_BY_EXTENSION_FOLLOWING_MAX_LENGTH 3 |
| 104 | +#define SORT_BY_EXTENSION_MAX_SEGMENTS 3 |
| 105 | + |
102 | 106 | typedef enum {
|
103 | 107 | SHOW_HIDDEN = 1 << 0,
|
104 | 108 | } FilterOptions;
|
@@ -126,6 +130,7 @@ static GQuark attribute_name_q,
|
126 | 130 | attribute_accessed_date_q,
|
127 | 131 | attribute_date_accessed_q,
|
128 | 132 | attribute_emblems_q,
|
| 133 | + attribute_extension_q, |
129 | 134 | attribute_mime_type_q,
|
130 | 135 | attribute_size_detail_q,
|
131 | 136 | attribute_size_on_disk_detail_q,
|
@@ -3160,6 +3165,186 @@ compare_by_full_path (CajaFile *file_1, CajaFile *file_2)
|
3160 | 3165 | return compare_by_display_name (file_1, file_2);
|
3161 | 3166 | }
|
3162 | 3167 |
|
| 3168 | +/* prev_extension_segment: |
| 3169 | + * @basename The basename of a file |
| 3170 | + * @rem_chars A pointer to the amount of remaining characters |
| 3171 | + * |
| 3172 | + * Finds the next segment delimiter to the left. A starting character of '.' is |
| 3173 | + * set to '\0'. |
| 3174 | + * |
| 3175 | + * Return value: The start of the previous segment (right of the dot) or |
| 3176 | + * basename if there are none remaining. |
| 3177 | + */ |
| 3178 | +static char * |
| 3179 | +prev_extension_segment (char *basename, int *rem_chars) |
| 3180 | +{ |
| 3181 | + if (*basename == '.') { |
| 3182 | + *basename = 0; |
| 3183 | + basename--; |
| 3184 | + (*rem_chars)--; |
| 3185 | + } |
| 3186 | + |
| 3187 | + while (*rem_chars > 0 && *basename != '.') { |
| 3188 | + (*rem_chars)--; |
| 3189 | + basename--; |
| 3190 | + } |
| 3191 | + |
| 3192 | + return basename + 1; |
| 3193 | +} |
| 3194 | + |
| 3195 | +/* is_valid_extension_segment: |
| 3196 | + * @segment Part of a modifiable zero-terminated string |
| 3197 | + * @segment_index The index of the current segment |
| 3198 | + * |
| 3199 | + * Uses a heuristic to identify valid file extensions. |
| 3200 | + * |
| 3201 | + * Return value: Whether the segment is part of the file extension. |
| 3202 | + */ |
| 3203 | +static gboolean |
| 3204 | +is_valid_extension_segment (const char *segment, int segment_index) |
| 3205 | +{ |
| 3206 | + gboolean result; |
| 3207 | + gboolean has_letters; |
| 3208 | + char c; |
| 3209 | + int char_offset; |
| 3210 | + switch (segment_index) { |
| 3211 | + case 0: |
| 3212 | + /* extremely long segments are probably not part of the extension */ |
| 3213 | + result = strlen (segment) < 20; |
| 3214 | + break; |
| 3215 | + default: |
| 3216 | + has_letters = FALSE; |
| 3217 | + char_offset = 0; |
| 3218 | + while (TRUE) { |
| 3219 | + c = *(segment + char_offset); |
| 3220 | + if (c == '\0') { |
| 3221 | + result = has_letters; |
| 3222 | + break; |
| 3223 | + } |
| 3224 | + /* allow digits if there are also letters */ |
| 3225 | + else if (isalpha (c)) { |
| 3226 | + has_letters = TRUE; |
| 3227 | + } |
| 3228 | + /* fail if it is neither digit nor letter */ |
| 3229 | + else if (!isdigit (c)) { |
| 3230 | + result = FALSE; |
| 3231 | + break; |
| 3232 | + } |
| 3233 | + |
| 3234 | + if (char_offset >= SORT_BY_EXTENSION_FOLLOWING_MAX_LENGTH) { |
| 3235 | + result = FALSE; |
| 3236 | + break; |
| 3237 | + } |
| 3238 | + char_offset++; |
| 3239 | + } |
| 3240 | + } |
| 3241 | + return result; |
| 3242 | +} |
| 3243 | + |
| 3244 | +static int |
| 3245 | +compare_by_extension_segments (CajaFile *file_1, CajaFile *file_2) |
| 3246 | +{ |
| 3247 | + char *name_1, *name_2; |
| 3248 | + char *segment_1, *segment_2; |
| 3249 | + int compare; |
| 3250 | + int rem_chars_1, rem_chars_2; |
| 3251 | + gboolean done_1, done_2; |
| 3252 | + gboolean is_directory_1, is_directory_2; |
| 3253 | + int segment_index; |
| 3254 | + |
| 3255 | + |
| 3256 | + /* Directories do not have an extension */ |
| 3257 | + is_directory_1 = caja_file_is_directory (file_1); |
| 3258 | + is_directory_2 = caja_file_is_directory (file_2); |
| 3259 | + |
| 3260 | + if (is_directory_1 && is_directory_2) { |
| 3261 | + return 0; |
| 3262 | + } else if (is_directory_1) { |
| 3263 | + return -1; |
| 3264 | + } else if (is_directory_2) { |
| 3265 | + return 1; |
| 3266 | + } |
| 3267 | + |
| 3268 | + name_1 = caja_file_get_display_name (file_1); |
| 3269 | + name_2 = caja_file_get_display_name (file_2); |
| 3270 | + rem_chars_1 = strlen (name_1); |
| 3271 | + rem_chars_2 = strlen (name_2); |
| 3272 | + |
| 3273 | + /* Point to one after the zero character */ |
| 3274 | + segment_1 = name_1 + rem_chars_1 + 1; |
| 3275 | + segment_2 = name_2 + rem_chars_2 + 1; |
| 3276 | + |
| 3277 | + segment_index = 0; |
| 3278 | + do { |
| 3279 | + segment_1 = prev_extension_segment (segment_1 - 1, &rem_chars_1); |
| 3280 | + segment_2 = prev_extension_segment (segment_2 - 1, &rem_chars_2); |
| 3281 | + |
| 3282 | + done_1 = rem_chars_1 <= 0 || !is_valid_extension_segment (segment_1, segment_index); |
| 3283 | + done_2 = rem_chars_2 <= 0 || !is_valid_extension_segment (segment_2, segment_index); |
| 3284 | + if (done_1 && !done_2) { |
| 3285 | + compare = -1; |
| 3286 | + break; |
| 3287 | + } |
| 3288 | + else if (!done_1 && done_2) { |
| 3289 | + compare = 1; |
| 3290 | + break; |
| 3291 | + } |
| 3292 | + else if (done_1 && done_2) { |
| 3293 | + compare = 0; |
| 3294 | + break; |
| 3295 | + } |
| 3296 | + |
| 3297 | + segment_index++; |
| 3298 | + if (segment_index > SORT_BY_EXTENSION_MAX_SEGMENTS - 1) { |
| 3299 | + break; |
| 3300 | + } |
| 3301 | + compare = strcmp (segment_1, segment_2); |
| 3302 | + } while (compare == 0); |
| 3303 | + |
| 3304 | + g_free (name_1); |
| 3305 | + g_free (name_2); |
| 3306 | + |
| 3307 | + return compare; |
| 3308 | +} |
| 3309 | + |
| 3310 | +static gchar * |
| 3311 | +caja_file_get_extension_as_string (CajaFile *file) |
| 3312 | +{ |
| 3313 | + char *name; |
| 3314 | + int rem_chars; |
| 3315 | + int segment_index; |
| 3316 | + char *segment; |
| 3317 | + char *right_segment; |
| 3318 | + char *result; |
| 3319 | + if (!caja_file_is_directory (file)) { |
| 3320 | + name = caja_file_get_display_name (file); |
| 3321 | + rem_chars = strlen (name); |
| 3322 | + segment = prev_extension_segment (name + rem_chars, &rem_chars); |
| 3323 | + |
| 3324 | + if (rem_chars > 0 && is_valid_extension_segment (segment, 0)) { |
| 3325 | + segment_index = 1; |
| 3326 | + do { |
| 3327 | + right_segment = segment; |
| 3328 | + segment = prev_extension_segment (segment - 1, &rem_chars); |
| 3329 | + if (rem_chars > 0 && is_valid_extension_segment (segment, segment_index)) { |
| 3330 | + /* remove zero-termination of segment */ |
| 3331 | + *(right_segment - 1) = '.'; |
| 3332 | + } |
| 3333 | + else { |
| 3334 | + break; |
| 3335 | + } |
| 3336 | + |
| 3337 | + segment_index++; |
| 3338 | + } while (segment_index < SORT_BY_EXTENSION_MAX_SEGMENTS + 1); |
| 3339 | + result = g_strdup (right_segment); |
| 3340 | + g_free (name); |
| 3341 | + return result; |
| 3342 | + } |
| 3343 | + g_free (name); |
| 3344 | + } |
| 3345 | + return g_strdup (""); |
| 3346 | +} |
| 3347 | + |
3163 | 3348 | static int
|
3164 | 3349 | caja_file_compare_for_sort_internal (CajaFile *file_1,
|
3165 | 3350 | CajaFile *file_2,
|
@@ -3286,6 +3471,12 @@ caja_file_compare_for_sort (CajaFile *file_1,
|
3286 | 3471 | result = compare_by_full_path (file_1, file_2);
|
3287 | 3472 | }
|
3288 | 3473 | break;
|
| 3474 | + case CAJA_FILE_SORT_BY_EXTENSION: |
| 3475 | + result = compare_by_extension_segments (file_1, file_2); |
| 3476 | + if (result == 0) { |
| 3477 | + result = compare_by_full_path (file_1, file_2); |
| 3478 | + } |
| 3479 | + break; |
3289 | 3480 | default:
|
3290 | 3481 | g_return_val_if_reached (0);
|
3291 | 3482 | }
|
@@ -3354,6 +3545,11 @@ caja_file_compare_for_sort_by_attribute_q (CajaFile *file_1,
|
3354 | 3545 | CAJA_FILE_SORT_BY_EMBLEMS,
|
3355 | 3546 | directories_first,
|
3356 | 3547 | reversed);
|
| 3548 | + } else if (attribute == attribute_extension_q) { |
| 3549 | + return caja_file_compare_for_sort (file_1, file_2, |
| 3550 | + CAJA_FILE_SORT_BY_EXTENSION, |
| 3551 | + directories_first, |
| 3552 | + reversed); |
3357 | 3553 | }
|
3358 | 3554 |
|
3359 | 3555 | /* it is a normal attribute, compare by strings */
|
@@ -6308,6 +6504,9 @@ caja_file_get_string_attribute_q (CajaFile *file, GQuark attribute_q)
|
6308 | 6504 | return caja_file_get_date_as_string (file,
|
6309 | 6505 | CAJA_DATE_TYPE_PERMISSIONS_CHANGED);
|
6310 | 6506 | }
|
| 6507 | + if (attribute_q == attribute_extension_q) { |
| 6508 | + return caja_file_get_extension_as_string (file); |
| 6509 | + } |
6311 | 6510 | if (attribute_q == attribute_permissions_q) {
|
6312 | 6511 | return caja_file_get_permissions_as_string (file);
|
6313 | 6512 | }
|
@@ -8332,6 +8531,7 @@ caja_file_class_init (CajaFileClass *class)
|
8332 | 8531 | attribute_accessed_date_q = g_quark_from_static_string ("accessed_date");
|
8333 | 8532 | attribute_date_accessed_q = g_quark_from_static_string ("date_accessed");
|
8334 | 8533 | attribute_emblems_q = g_quark_from_static_string ("emblems");
|
| 8534 | + attribute_extension_q = g_quark_from_static_string ("extension"); |
8335 | 8535 | attribute_mime_type_q = g_quark_from_static_string ("mime_type");
|
8336 | 8536 | attribute_size_detail_q = g_quark_from_static_string ("size_detail");
|
8337 | 8537 | attribute_size_on_disk_detail_q = g_quark_from_static_string ("size_on_disk_detail");
|
|
0 commit comments