From 916a02017333ac32b8058d3c397eeb4ec85b742b Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 15 Aug 2012 22:44:21 +0200
Subject: Song: add function song_dup_detached()

Initial support for "detached" songs that come from the database, but
are private copies.
---
 src/Song.cxx | 36 ++++++++++++++++++++++++++++++++++++
 src/mapper.c | 21 ++++++++++++++++++++-
 src/song.h   | 25 +++++++++++++++++++++++++
 3 files changed, 81 insertions(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/Song.cxx b/src/Song.cxx
index 3dfdc0c74..2ef690fb0 100644
--- a/src/Song.cxx
+++ b/src/Song.cxx
@@ -29,6 +29,8 @@ extern "C" {
 
 #include <assert.h>
 
+struct directory detached_root;
+
 static struct song *
 song_alloc(const char *uri, struct directory *parent)
 {
@@ -76,6 +78,27 @@ song_replace_uri(struct song *old_song, const char *uri)
 	return new_song;
 }
 
+struct song *
+song_dup_detached(const struct song *src)
+{
+	assert(src != nullptr);
+
+	struct song *song;
+	if (song_in_database(src)) {
+		char *uri = song_get_uri(src);
+		song = song_alloc(uri, &detached_root);
+		g_free(uri);
+	} else
+		song = song_alloc(src->uri, nullptr);
+
+	song->tag = tag_dup(src->tag);
+	song->mtime = src->mtime;
+	song->start_ms = src->start_ms;
+	song->end_ms = src->end_ms;
+
+	return song;
+}
+
 void
 song_free(struct song *song)
 {
@@ -107,6 +130,19 @@ song_equals(const struct song *a, const struct song *b)
 	assert(a != nullptr);
 	assert(b != nullptr);
 
+	if (a->parent != nullptr && b->parent != nullptr &&
+	    !directory_equals(*a->parent, *b->parent) &&
+	    (a->parent == &detached_root || b->parent == &detached_root)) {
+		/* must compare the full URI if one of the objects is
+		   "detached" */
+		char *au = song_get_uri(a);
+		char *bu = song_get_uri(b);
+		const bool result = strcmp(au, bu) == 0;
+		g_free(bu);
+		g_free(au);
+		return result;
+	}
+
 	return directory_is_same(a->parent, b->parent) &&
 		strcmp(a->uri, b->uri) == 0;
 }
diff --git a/src/mapper.c b/src/mapper.c
index 7db74b1af..1f8a54b46 100644
--- a/src/mapper.c
+++ b/src/mapper.c
@@ -219,13 +219,32 @@ map_directory_child_fs(const struct directory *directory, const char *name)
 	return path;
 }
 
+/**
+ * Map a song object that was created by song_dup_detached().  It does
+ * not have a real parent directory, only the dummy object
+ * #detached_root.
+ */
+static char *
+map_detached_song_fs(const char *uri_utf8)
+{
+	char *uri_fs = utf8_to_fs_charset(uri_utf8);
+	if (uri_fs == NULL)
+		return NULL;
+
+	char *path = g_build_filename(music_dir_fs, uri_fs, NULL);
+	g_free(uri_fs);
+	return path;
+}
+
 char *
 map_song_fs(const struct song *song)
 {
 	assert(song_is_file(song));
 
 	if (song_in_database(song))
-		return map_directory_child_fs(song->parent, song->uri);
+		return song_is_detached(song)
+			? map_detached_song_fs(song->uri)
+			: map_directory_child_fs(song->parent, song->uri);
 	else
 		return utf8_to_fs_charset(song->uri);
 }
diff --git a/src/song.h b/src/song.h
index 366ffc1a1..a09b69df2 100644
--- a/src/song.h
+++ b/src/song.h
@@ -23,6 +23,7 @@
 #include "util/list.h"
 #include "gcc.h"
 
+#include <assert.h>
 #include <stddef.h>
 #include <stdbool.h>
 #include <sys/time.h>
@@ -59,6 +60,12 @@ struct song {
 	char uri[sizeof(int)];
 };
 
+/**
+ * A dummy #directory instance that is used for "detached" song
+ * copies.
+ */
+extern struct directory detached_root;
+
 G_BEGIN_DECLS
 
 /** allocate a new song with a remote URL */
@@ -86,6 +93,15 @@ song_file_load(const char *path, struct directory *parent);
 struct song *
 song_replace_uri(struct song *song, const char *uri);
 
+/**
+ * Creates a duplicate of the song object.  If the object is in the
+ * database, it creates a "detached" copy of this song, see
+ * song_is_detached().
+ */
+gcc_malloc
+struct song *
+song_dup_detached(const struct song *src);
+
 void
 song_free(struct song *song);
 
@@ -101,6 +117,15 @@ song_is_file(const struct song *song)
 	return song_in_database(song) || song->uri[0] == '/';
 }
 
+static inline bool
+song_is_detached(const struct song *song)
+{
+	assert(song != NULL);
+	assert(song_in_database(song));
+
+	return song->parent == &detached_root;
+}
+
 /**
  * Returns true if both objects refer to the same physical song.
  */
-- 
cgit v1.2.3