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