Skip to content

Commit

Permalink
Starred Station save to Playlist
Browse files Browse the repository at this point in the history
  • Loading branch information
technosf committed Dec 26, 2024
1 parent b6166d4 commit 41953d2
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 122 deletions.
10 changes: 10 additions & 0 deletions data/com.github.louis77.tuner.appdata.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@
</provides>

<releases>
<release version="1.5.6" date="2024-12-26">
<description>
<p>Intermediate release prior to new Version:</p>
<ul>
<li>Starred Stations can be exported to an M3U file from the Preferences menu</li>
<li>Export your starred stations prior to upgrading to forthcoming v2 release</li>
<li>Starred Stations and other settings need to be reapplied to v2 due to breaking changes</li>
</ul>
</description>
</release>
<release version="1.5.5" date="2024-11-08">
<description>
<p>Maintanance release:</p>
Expand Down
2 changes: 1 addition & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
project (
'com.github.louis77.tuner',
'vala', 'c',
version: '1.5.5',
version: '1.5.6',
meson_version: '>= 0.60.0',
)

Expand Down
237 changes: 128 additions & 109 deletions src/Models/StationStore.vala
Original file line number Diff line number Diff line change
Expand Up @@ -15,127 +15,146 @@ using Gee;

namespace Tuner.Model {

public class StationStore : Object {
private ArrayList<Station> _store;
private File _favorites_file;
public class StationStore : Object
{
private const string M3U8 = "#EXTM3U\n#EXTENC:UTF-8\n#PLAYLIST:Tuner\n";
private const string M3U8_UUID = "STATIONUUID";

private ArrayList<Station> _store;
private File _favorites_file;

public StationStore (string favorites_path) {
Object ();

_store = new ArrayList<Station> ();
_favorites_file = File.new_for_path (favorites_path);
ensure ();
load ();
debug (@"store initialized in path $favorites_path");
}

public StationStore (string favorites_path) {
Object ();

_store = new ArrayList<Station> ();
_favorites_file = File.new_for_path (favorites_path);
ensure ();
load ();
debug (@"store initialized in path $favorites_path");
}

public void add (Station station) {
_add (station);
persist ();
}

private void _add (Station station) {
_store.add (station);
// TODO Should we do a sorted insert?
}

public void remove (Station station) {
_store.remove (station);
persist ();
}

private void ensure () {
// Non-racy approach is to try to create the file first
// and ignore errors if it already exists
try {
var df = _favorites_file.create (FileCreateFlags.PRIVATE);
df.close ();
debug (@"store created");
} catch (Error e) {
// Ignore, file already existed, which is good
public void add (Station station) {
_add (station);
persist ();
}
}

private void load () {
debug ("loading store");
Json.Parser parser = new Json.Parser ();

try {
var stream = _favorites_file.read ();
parser.load_from_stream (stream);
stream.close ();
} catch (Error e) {
warning (@"Load failed with error: $(e.message)");
private void _add (Station station) {
_store.add (station);
// TODO Should we do a sorted insert?
}

Json.Node? node = parser.get_root ();
public void remove (Station station) {
_store.remove (station);
persist ();
}

if ( node == null ) return; // No favorites store
private void ensure () {
// Non-racy approach is to try to create the file first
// and ignore errors if it already exists
try {
var df = _favorites_file.create (FileCreateFlags.PRIVATE);
df.close ();
debug (@"store created");
} catch (Error e) {
// Ignore, file already existed, which is good
}
}

Json.Array array = node.get_array (); // Json-CRITICAL **: 21:02:51.821: json_node_get_array: assertion 'JSON_NODE_IS_VALID (node)' failed
array.foreach_element ((a, i, elem) => { // json_array_foreach_element: assertion 'array != NULL' failed
Station station = Json.gobject_deserialize (typeof (Station), elem) as Station;
// TODO This should probably not be here but in
// DirectoryController
station.notify["starred"].connect ( (sender, property) => {
if (station.starred) {
this.add (station);
} else {
this.remove (station);
}
private void load () {
debug ("loading store");
Json.Parser parser = new Json.Parser ();

try {
var stream = _favorites_file.read ();
parser.load_from_stream (stream);
stream.close ();
} catch (Error e) {
warning (@"Load failed with error: $(e.message)");
}

Json.Node? node = parser.get_root ();

if ( node == null ) return; // No favorites store

Json.Array array = node.get_array (); // Json-CRITICAL **: 21:02:51.821: json_node_get_array: assertion 'JSON_NODE_IS_VALID (node)' failed
array.foreach_element ((a, i, elem) => { // json_array_foreach_element: assertion 'array != NULL' failed
Station station = Json.gobject_deserialize (typeof (Station), elem) as Station;
// TODO This should probably not be here but in
// DirectoryController
station.notify["starred"].connect ( (sender, property) => {
if (station.starred) {
this.add (station);
} else {
this.remove (station);
}
});

_add (station);
});

_add (station);
});

debug (@"loaded store size: $(_store.size)");
}
debug (@"loaded store size: $(_store.size)");
}

private void persist () {
debug ("persisting store");
var data = serialize ();

try {
_favorites_file.delete ();
var stream = _favorites_file.create (
FileCreateFlags.REPLACE_DESTINATION | FileCreateFlags.PRIVATE
);
var s = new DataOutputStream (stream);
s.put_string (data);
s.flush ();
s.close (); // closes base stream also
} catch (Error e) {
warning (@"Persist failed with error: $(e.message)");
private void persist () {
debug ("persisting store");
var data = serialize ();

try {
_favorites_file.delete ();
var stream = _favorites_file.create (
FileCreateFlags.REPLACE_DESTINATION | FileCreateFlags.PRIVATE
);
var s = new DataOutputStream (stream);
s.put_string (data);
s.flush ();
s.close (); // closes base stream also
} catch (Error e) {
warning (@"Persist failed with error: $(e.message)");
}
}
}

public string serialize () {
Json.Builder builder = new Json.Builder ();
builder.begin_array ();
foreach (var station in _store) {
var node = Json.gobject_serialize (station);
builder.add_value (node);

public string serialize () {
Json.Builder builder = new Json.Builder ();
builder.begin_array ();
foreach (var station in _store) {
var node = Json.gobject_serialize (station);
builder.add_value (node);
}
builder.end_array ();

Json.Generator generator = new Json.Generator ();
generator.set_root (builder.get_root ());
string data = generator.to_data (null);
return data;
}
builder.end_array ();

Json.Generator generator = new Json.Generator ();
generator.set_root (builder.get_root ());
string data = generator.to_data (null);
return data;
}

public ArrayList<Station> get_all () {
return _store;
}

public bool contains (Station station) {
foreach (var s in _store) {
if (s.id == station.id) {
return true;

public ArrayList<Station> get_all () {
return _store;
}

public bool contains (Station station) {
foreach (var s in _store) {
if (s.id == station.id) {
return true;
}
}
return false;
}
return false;
}
}

}
/**
* @brief Creates a string of Starred stations in m3u format
* @return A string representation of the favorites formated as M3U
*/
public string export_m3u8()
{
StringBuilder playlist = new StringBuilder(M3U8);
foreach ( var station in _store)
{
playlist.append (@"#EXTINF:-1,$(station.title) - logo=\"$(station.favicon_url)\",$M3U8_UUID=\"$(station.id)\"\n$(station.url)\n#EXTIMG:$(station.favicon_url)\n");
}

return playlist.str;

} // export_m3u8
} // StationStore
} // Tuner.Model
Loading

0 comments on commit 41953d2

Please sign in to comment.