1 /*
2  * Copyright 2003-2021 The Music Player Daemon Project
3  * http://www.musicpd.org
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include "Song.hxx"
21 #include "ExportedSong.hxx"
22 #include "Directory.hxx"
23 #include "tag/Tag.hxx"
24 #include "tag/Builder.hxx"
25 #include "song/DetachedSong.hxx"
26 #include "song/LightSong.hxx"
27 #include "fs/Traits.hxx"
28 #include "time/ChronoUtil.hxx"
29 #include "util/IterableSplitString.hxx"
30 
Song(DetachedSong && other,Directory & _parent)31 Song::Song(DetachedSong &&other, Directory &_parent) noexcept
32 	:parent(_parent),
33 	 filename(other.GetURI()),
34 	 tag(std::move(other.WritableTag())),
35 	 mtime(other.GetLastModified()),
36 	 start_time(other.GetStartTime()),
37 	 end_time(other.GetEndTime()),
38 	 audio_format(other.GetAudioFormat())
39 {
40 }
41 
42 const char *
GetFilenameSuffix() const43 Song::GetFilenameSuffix() const noexcept
44 {
45 	return target.empty()
46 		? PathTraitsUTF8::GetFilenameSuffix(filename.c_str())
47 		: PathTraitsUTF8::GetPathSuffix(target.c_str());
48 }
49 
50 std::string
GetURI() const51 Song::GetURI() const noexcept
52 {
53 	if (parent.IsRoot())
54 		return filename;
55 	else {
56 		const char *path = parent.GetPath();
57 		return PathTraitsUTF8::Build(path, filename);
58 	}
59 }
60 
61 /**
62  * Path name traversal of a #Directory.
63  */
64 gcc_pure
65 static const Directory *
FindTargetDirectory(const Directory & base,StringView path)66 FindTargetDirectory(const Directory &base, StringView path) noexcept
67 {
68 	const auto *directory = &base;
69 	for (const StringView name : IterableSplitString(path, '/')) {
70 		if (name.empty() || name.Equals("."))
71 			continue;
72 
73 		directory = name.Equals("..")
74 			? directory->parent
75 			: directory->FindChild(name);
76 		if (directory == nullptr)
77 			break;
78 	}
79 
80 	return directory;
81 }
82 
83 /**
84  * Path name traversal of a #Song.
85  */
86 gcc_pure
87 static const Song *
FindTargetSong(const Directory & _directory,StringView target)88 FindTargetSong(const Directory &_directory, StringView target) noexcept
89 {
90 	auto [path, last] = target.SplitLast('/');
91 	if (last == nullptr) {
92 		last = path;
93 		path = nullptr;
94 	}
95 
96 	if (last.empty())
97 		return nullptr;
98 
99 	const auto *directory = FindTargetDirectory(_directory, path);
100 	if (directory == nullptr)
101 		return nullptr;
102 
103 	return directory->FindSong(last);
104 }
105 
106 ExportedSong
Export() const107 Song::Export() const noexcept
108 {
109 	const auto *target_song = !target.empty()
110 		? FindTargetSong(parent, (std::string_view)target)
111 		: nullptr;
112 
113 	Tag merged_tag;
114 	if (target_song != nullptr) {
115 		/* if we found the target song (which may be the
116 		   underlying song file of a CUE file), merge the tags
117 		   from that song with this song's tags (from the CUE
118 		   file) */
119 		TagBuilder builder(tag);
120 		builder.Complement(target_song->tag);
121 		merged_tag = builder.Commit();
122 	}
123 
124 	ExportedSong dest = merged_tag.IsDefined()
125 		? ExportedSong(filename.c_str(), std::move(merged_tag))
126 		: ExportedSong(filename.c_str(), tag);
127 	if (!parent.IsRoot())
128 		dest.directory = parent.GetPath();
129 	if (!target.empty())
130 		dest.real_uri = target.c_str();
131 	dest.mtime = IsNegative(mtime) && target_song != nullptr
132 		? target_song->mtime
133 		: mtime;
134 	dest.start_time = start_time.IsZero() && target_song != nullptr
135 		? target_song->start_time
136 		: start_time;
137 	dest.end_time = end_time.IsZero() && target_song != nullptr
138 		? target_song->end_time
139 		: end_time;
140 	dest.audio_format = audio_format.IsDefined() || target_song == nullptr
141 		? audio_format
142 		: target_song->audio_format;
143 	return dest;
144 }
145