diff options
Diffstat (limited to 'src/util/file.vala')
-rw-r--r-- | src/util/file.vala | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/src/util/file.vala b/src/util/file.vala new file mode 100644 index 0000000..1b6bb6c --- /dev/null +++ b/src/util/file.vala @@ -0,0 +1,241 @@ +/* Copyright 2009-2014 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +// Returns true if the file is claimed, false if it exists, and throws an Error otherwise. The file +// will be created when the function exits and should be overwritten. Note that the file is not +// held open; claiming a file is merely based on its existence. +// +// This function is thread-safe. +public bool claim_file(File file) throws Error { + try { + file.create(FileCreateFlags.NONE, null); + + // created; success + return true; + } catch (Error err) { + // check for file-exists error + if (!(err is IOError.EXISTS)) { + warning("claim_file %s: %s", file.get_path(), err.message); + + throw err; + } + + return false; + } +} + +// This function "claims" a file on the filesystem in the directory specified with a basename the +// same or similar as what has been requested (adds numerals to the end of the name until a unique +// one has been found). The file may exist when this function returns, and it should be +// overwritten. It does *not* attempt to create the parent directory, however. +// +// This function is thread-safe. +public File? generate_unique_file(File dir, string basename, out bool collision) throws Error { + // create the file to atomically "claim" it + File file = dir.get_child(basename); + if (claim_file(file)) { + collision = false; + + return file; + } + + // file exists, note collision and keep searching + collision = true; + + string name, ext; + disassemble_filename(basename, out name, out ext); + + // generate a unique filename + for (int ctr = 1; ctr < int.MAX; ctr++) { + string new_name = (ext != null) ? "%s_%d.%s".printf(name, ctr, ext) : "%s_%d".printf(name, ctr); + + file = dir.get_child(new_name); + if (claim_file(file)) + return file; + } + + warning("generate_unique_filename %s for %s: unable to claim file", dir.get_path(), basename); + + return null; +} + +public void disassemble_filename(string basename, out string name, out string ext) { + long offset = find_last_offset(basename, '.'); + if (offset <= 0) { + name = basename; + ext = null; + } else { + name = basename.substring(0, offset); + ext = basename.substring(offset + 1, -1); + } +} + +// This function is thread-safe. +public uint64 query_total_file_size(File file_or_dir, Cancellable? cancellable = null) throws Error { + FileType type = file_or_dir.query_file_type(FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); + if (type == FileType.REGULAR) { + FileInfo info = null; + try { + info = file_or_dir.query_info(FileAttribute.STANDARD_SIZE, + FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable); + } catch (Error err) { + if (err is IOError.CANCELLED) + throw err; + + debug("Unable to query filesize for %s: %s", file_or_dir.get_path(), err.message); + + return 0; + } + + return info.get_size(); + } else if (type != FileType.DIRECTORY) { + return 0; + } + + FileEnumerator enumerator; + try { + enumerator = file_or_dir.enumerate_children(FileAttribute.STANDARD_NAME, + FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable); + if (enumerator == null) + return 0; + } catch (Error err) { + // Don't treat a permissions failure as a hard failure, just skip the directory + if (err is FileError.PERM || err is IOError.PERMISSION_DENIED) + return 0; + + throw err; + } + + uint64 total_bytes = 0; + + FileInfo info = null; + while ((info = enumerator.next_file(cancellable)) != null) + total_bytes += query_total_file_size(file_or_dir.get_child(info.get_name()), cancellable); + + return total_bytes; +} + +// Does not currently recurse. Could be modified to do so. Does not error out on first file that +// does not delete, but logs a warning and continues. +// Note: if supplying a progress monitor, a file count is also required. The count_files_in_directory() +// function below should do the trick. +public void delete_all_files(File dir, Gee.Set<string>? exceptions = null, ProgressMonitor? monitor = null, + uint64 file_count = 0, Cancellable? cancellable = null) throws Error { + FileType type = dir.query_file_type(FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); + if (type != FileType.DIRECTORY) + throw new IOError.NOT_DIRECTORY("%s is not a directory".printf(dir.get_path())); + + FileEnumerator enumerator = dir.enumerate_children("standard::name,standard::type", + FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable); + FileInfo info = null; + uint64 i = 0; + while ((info = enumerator.next_file(cancellable)) != null) { + if (info.get_file_type() != FileType.REGULAR) + continue; + + if (exceptions != null && exceptions.contains(info.get_name())) + continue; + + File file = dir.get_child(info.get_name()); + try { + file.delete(cancellable); + } catch (Error err) { + warning("Unable to delete file %s: %s", file.get_path(), err.message); + } + + if (monitor != null && file_count > 0) + monitor(file_count, ++i); + } +} + +public time_t query_file_modified(File file) throws Error { + FileInfo info = file.query_info(FileAttribute.TIME_MODIFIED, FileQueryInfoFlags.NOFOLLOW_SYMLINKS, + null); + + return info.get_modification_time().tv_sec; +} + +public bool query_is_directory(File file) { + return file.query_file_type(FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null) == FileType.DIRECTORY; +} + +public bool query_is_directory_empty(File dir) throws Error { + if (dir.query_file_type(FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null) != FileType.DIRECTORY) + return false; + + FileEnumerator enumerator = dir.enumerate_children("standard::name", + FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); + if (enumerator == null) + return false; + + return enumerator.next_file(null) == null; +} + +public string get_display_pathname(File file) { + // attempt to replace home path with tilde in a user-pleasable way + string path = file.get_parse_name(); + string home = Environment.get_home_dir(); + + if (path == home) + return "~"; + + if (path.has_prefix(home)) + return "~%s".printf(path.substring(home.length)); + + return path; +} + +public string strip_pretty_path(string path) { + if (!path.has_prefix("~")) + return path; + + return Environment.get_home_dir() + path.substring(1); +} + +public string? get_file_info_id(FileInfo info) { + return info.get_attribute_string(FileAttribute.ID_FILE); +} + +// Breaks a uint64 skip amount into several smaller skips. +public void skip_uint64(InputStream input, uint64 skip_amount) throws GLib.Error { + while (skip_amount > 0) { + // skip() throws an error if the amount is too large, so check against ssize_t.MAX + if (skip_amount >= ssize_t.MAX) { + input.skip(ssize_t.MAX); + skip_amount -= ssize_t.MAX; + } else { + input.skip((size_t) skip_amount); + skip_amount = 0; + } + } +} + +// Returns the number of files (and/or directories) within a directory. +public uint64 count_files_in_directory(File dir) throws GLib.Error { + if (!query_is_directory(dir)) + return 0; + + uint64 count = 0; + FileEnumerator enumerator = dir.enumerate_children("standard::*", + FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); + + FileInfo info = null; + while ((info = enumerator.next_file()) != null) + count++; + + return count; +} + +// Replacement for deprecated Gio.file_equal +public bool file_equal(File? a, File? b) { + return (a != null && b != null) ? a.equal(b) : false; +} + +// Replacement for deprecated Gio.file_hash +public uint file_hash(File? file) { + return file != null ? file.hash() : 0; +} + |