summaryrefslogtreecommitdiff
path: root/src/Tombstone.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/Tombstone.vala')
-rw-r--r--src/Tombstone.vala336
1 files changed, 336 insertions, 0 deletions
diff --git a/src/Tombstone.vala b/src/Tombstone.vala
new file mode 100644
index 0000000..d25b979
--- /dev/null
+++ b/src/Tombstone.vala
@@ -0,0 +1,336 @@
+/* Copyright 2010-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.
+ */
+
+public class TombstoneSourceCollection : DatabaseSourceCollection {
+ private Gee.HashMap<File, Tombstone> file_map = new Gee.HashMap<File, Tombstone>(file_hash,
+ file_equal);
+
+ public TombstoneSourceCollection() {
+ base ("Tombstones", get_tombstone_id);
+ }
+
+ public override bool holds_type_of_source(DataSource source) {
+ return source is Tombstone;
+ }
+
+ private static int64 get_tombstone_id(DataSource source) {
+ return ((Tombstone) source).get_tombstone_id().id;
+ }
+
+ protected override void notify_contents_altered(Gee.Iterable<DataObject>? added,
+ Gee.Iterable<DataObject>? removed) {
+ if (added != null) {
+ foreach (DataObject object in added) {
+ Tombstone tombstone = (Tombstone) object;
+
+ file_map.set(tombstone.get_file(), tombstone);
+ }
+ }
+
+ if (removed != null) {
+ foreach (DataObject object in removed) {
+ Tombstone tombstone = (Tombstone) object;
+
+ // do we actually have this file?
+ if (file_map.has_key(tombstone.get_file())) {
+ // yes, try to remove it.
+ bool is_removed = file_map.unset(tombstone.get_file());
+ assert(is_removed);
+ }
+ // if the hashmap didn't have the file to begin with,
+ // we're already in the state we wanted to be in, so our
+ // work is done; no need to assert.
+ }
+ }
+
+ base.notify_contents_altered(added, removed);
+ }
+
+ protected override void notify_items_altered(Gee.Map<DataObject, Alteration> items) {
+ foreach (DataObject object in items.keys) {
+ Alteration alteration = items.get(object);
+ if (!alteration.has_subject("file"))
+ continue;
+
+ Tombstone tombstone = (Tombstone) object;
+
+ foreach (string detail in alteration.get_details("file")) {
+ File old_file = File.new_for_path(detail);
+
+ bool removed = file_map.unset(old_file);
+ assert(removed);
+
+ file_map.set(tombstone.get_file(), tombstone);
+
+ break;
+ }
+ }
+ }
+
+ public Tombstone? locate(File file) {
+ return file_map.get(file);
+ }
+
+ public bool matches(File file) {
+ return file_map.has_key(file);
+ }
+
+ public void resurrect(Tombstone tombstone) {
+ destroy_marked(mark(tombstone), false);
+ }
+
+ public void resurrect_many(Gee.Collection<Tombstone> tombstones) {
+ Marker marker = mark_many(tombstones);
+
+ freeze_notifications();
+ DatabaseTable.begin_transaction();
+
+ destroy_marked(marker, false);
+
+ try {
+ DatabaseTable.commit_transaction();
+ } catch (DatabaseError err) {
+ AppWindow.database_error(err);
+ }
+
+ thaw_notifications();
+ }
+
+ // This initiates a scan of the tombstoned files, resurrecting them if the file is no longer
+ // present on disk. If a DirectoryMonitor is supplied, the scan will use that object's FileInfo
+ // if available. If not available or not supplied, the scan will query for the file's
+ // existence.
+ //
+ // Note that this call is non-blocking.
+ public void launch_scan(DirectoryMonitor? monitor, Cancellable? cancellable) {
+ async_scan.begin(monitor, cancellable);
+ }
+
+ private async void async_scan(DirectoryMonitor? monitor, Cancellable? cancellable) {
+ // search through all tombstones for missing files, which indicate the tombstone can go away
+ Marker marker = start_marking();
+ foreach (DataObject object in get_all()) {
+ Tombstone tombstone = (Tombstone) object;
+ File file = tombstone.get_file();
+
+ FileInfo? info = null;
+ if (monitor != null)
+ info = monitor.get_file_info(file);
+
+ // Want to be conservative here; only resurrect a tombstone if file is actually detected
+ // as not present, and not some other problem (which may be intermittant)
+ if (info == null) {
+ try {
+ info = yield file.query_info_async(FileAttribute.STANDARD_NAME,
+ FileQueryInfoFlags.NOFOLLOW_SYMLINKS, Priority.LOW, cancellable);
+ } catch (Error err) {
+ // watch for cancellation, which signals it's time to go
+ if (err is IOError.CANCELLED)
+ break;
+
+ if (!(err is IOError.NOT_FOUND)) {
+ warning("Unable to check for existence of tombstoned file %s: %s",
+ file.get_path(), err.message);
+ }
+ }
+ }
+
+ // if not found, resurrect
+ if (info == null)
+ marker.mark(tombstone);
+
+ Idle.add(async_scan.callback);
+ yield;
+ }
+
+ if (marker.get_count() > 0) {
+ debug("Resurrecting %d tombstones with no backing file", marker.get_count());
+ DatabaseTable.begin_transaction();
+ destroy_marked(marker, false);
+ try {
+ DatabaseTable.commit_transaction();
+ } catch (DatabaseError err2) {
+ AppWindow.database_error(err2);
+ }
+ }
+ }
+}
+
+public class TombstonedFile {
+ public File file;
+ public int64 filesize;
+ public string? md5;
+
+ public TombstonedFile(File file, int64 filesize, string? md5) {
+ this.file = file;
+ this.filesize = filesize;
+ this.md5 = md5;
+ }
+}
+
+public class Tombstone : DataSource {
+ // These values are persisted. Do not change.
+ public enum Reason {
+ REMOVED_BY_USER = 0,
+ AUTO_DETECTED_DUPLICATE = 1;
+
+ public int serialize() {
+ return (int) this;
+ }
+
+ public static Reason unserialize(int value) {
+ switch ((Reason) value) {
+ case AUTO_DETECTED_DUPLICATE:
+ return AUTO_DETECTED_DUPLICATE;
+
+ // 0 is the default in the database, so it should remain so here
+ case REMOVED_BY_USER:
+ default:
+ return REMOVED_BY_USER;
+ }
+ }
+ }
+
+ public static TombstoneSourceCollection global = null;
+
+ private TombstoneRow row;
+ private File? file = null;
+
+ private Tombstone(TombstoneRow row) {
+ this.row = row;
+ }
+
+ public static void init() {
+ global = new TombstoneSourceCollection();
+
+ TombstoneRow[]? rows = null;
+ try {
+ rows = TombstoneTable.get_instance().fetch_all();
+ } catch (DatabaseError err) {
+ AppWindow.database_error(err);
+ }
+
+ if (rows != null) {
+ Gee.ArrayList<Tombstone> tombstones = new Gee.ArrayList<Tombstone>();
+ foreach (TombstoneRow row in rows)
+ tombstones.add(new Tombstone(row));
+
+ global.add_many(tombstones);
+ }
+ }
+
+ public static void terminate() {
+ }
+
+ public static void entomb_many_sources(Gee.Collection<MediaSource> sources, Reason reason)
+ throws DatabaseError {
+ Gee.Collection<TombstonedFile> files = new Gee.ArrayList<TombstonedFile>();
+ foreach (MediaSource source in sources) {
+ foreach (BackingFileState state in source.get_backing_files_state())
+ files.add(new TombstonedFile(state.get_file(), state.filesize, state.md5));
+ }
+
+ entomb_many_files(files, reason);
+ }
+
+ public static void entomb_many_files(Gee.Collection<TombstonedFile> files, Reason reason)
+ throws DatabaseError {
+ // destroy any out-of-date tombstones so they may be updated
+ Marker to_destroy = global.start_marking();
+ foreach (TombstonedFile file in files) {
+ Tombstone? tombstone = global.locate(file.file);
+ if (tombstone != null)
+ to_destroy.mark(tombstone);
+ }
+
+ global.destroy_marked(to_destroy, false);
+
+ Gee.ArrayList<Tombstone> tombstones = new Gee.ArrayList<Tombstone>();
+ foreach (TombstonedFile file in files) {
+ tombstones.add(new Tombstone(TombstoneTable.get_instance().add(file.file.get_path(),
+ file.filesize, file.md5, reason)));
+ }
+
+ global.add_many(tombstones);
+ }
+
+ public override string get_typename() {
+ return "tombstone";
+ }
+
+ public override int64 get_instance_id() {
+ return get_tombstone_id().id;
+ }
+
+ public override string get_name() {
+ return row.filepath;
+ }
+
+ public override string to_string() {
+ return "Tombstone %s".printf(get_name());
+ }
+
+ public TombstoneID get_tombstone_id() {
+ return row.id;
+ }
+
+ public File get_file() {
+ if (file == null)
+ file = File.new_for_path(row.filepath);
+
+ return file;
+ }
+
+ public string? get_md5() {
+ return is_string_empty(row.md5) ? null : row.md5;
+ }
+
+ public Reason get_reason() {
+ return row.reason;
+ }
+
+ public void move(File file) {
+ try {
+ TombstoneTable.get_instance().update_file(row.id, file.get_path());
+ } catch (DatabaseError err) {
+ AppWindow.database_error(err);
+ }
+
+ string old_filepath = row.filepath;
+ row.filepath = file.get_path();
+ this.file = file;
+
+ notify_altered(new Alteration("file", old_filepath));
+ }
+
+ public bool matches(File file, int64 filesize, string? md5) {
+ if (row.filesize != filesize)
+ return false;
+
+ // normalize to deal with empty strings
+ string? this_md5 = is_string_empty(row.md5) ? null : row.md5;
+ string? other_md5 = is_string_empty(md5) ? null : md5;
+
+ if (this_md5 != other_md5)
+ return false;
+
+ if (!get_file().equal(file))
+ return false;
+
+ return true;
+ }
+
+ public override void destroy() {
+ try {
+ TombstoneTable.get_instance().remove(row.id);
+ } catch (DatabaseError err) {
+ AppWindow.database_error(err);
+ }
+
+ base.destroy();
+ }
+}
+