Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/Services/PlaylistStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ public function insertNewFromProvider(int $providerId, ProviderStore $ps): array
$stmt->execute([$providerId]);
$have = array_flip(array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN)));

// Genuinely-new provider channels, in provider order (group_title, id).
// Genuinely-new provider channels, in provider group-pane order (group position_order, id).
$newRows = [];
$ps->streamForSeed(function (array $r) use (&$newRows, $have) {
if (! isset($have[(int) $r['id']])) {
Expand Down
13 changes: 11 additions & 2 deletions app/Services/ProviderStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -433,10 +433,19 @@ public function streamGuideProgrammesForIds(array $tvgIds, int $fromTs, callable
}
}

/** Stream every channel (id + group + minimal data) ordered by group then id — used to seed playlists. */
/** Stream every channel (id + group + minimal data) in group-pane order — used to seed playlists. */
public function streamForSeed(callable $cb): void
{
$stmt = $this->db->query('SELECT id, group_title, name, url, tvg_id, tvg_logo, tvg_name FROM channels ORDER BY group_title, id');
// Emit channels in GROUP-PANE order (groups.position_order), not alphabetically by title,
// so a freshly-seeded playlist's flat channel order matches its group order — otherwise the
// group pane and the channel list disagree (e.g. group #1 "WORLD CUP" but the first channels
// are "ARABIC"). Matches groups() ORDER BY exactly; channels whose group has no groups row
// (orphans, added as trailing panes later) sort last. Within a group, id order.
$stmt = $this->db->query(
'SELECT c.id, c.group_title, c.name, c.url, c.tvg_id, c.tvg_logo, c.tvg_name
FROM channels c LEFT JOIN groups g ON g.group_title = c.group_title
ORDER BY COALESCE(g.position_order, 1e12), c.group_title COLLATE NOCASE, c.id'
);
while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
$cb($r);
}
Expand Down
18 changes: 16 additions & 2 deletions tests/Feature/PlaylistTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ public function test_create_seeds_from_provider(): void {
$rows=$st->groups(); $this->assertSame('US-ENT',$rows[0]['group_title']); // seeded in provider group order
}

public function test_seed_flat_order_follows_group_order_not_alphabetical(): void {
// providerWithStore seeds groups US-ENT(10) then CANADA(20) — deliberately NON-alphabetical
// (CANADA sorts first). The flat channel order must follow the GROUP order (US-ENT block first),
// not the alphabetical group_title order the seed used to produce.
$u=User::factory()->create(['email_verified_at'=>now()]);
$p=$this->providerWithStore($u);
$this->actingAs($u)->postJson('/playlists',['name'=>'PL','providers'=>[$p->id]])->assertOk();
$pl=Playlist::first(); $st=new PlaylistStore($pl->id);
$flat=array_map(fn($r)=>$r['group_title'],$st->channels(100,0));
$this->assertSame(['US-ENT','US-ENT','CANADA','CANADA','CANADA'],$flat);
// group pane order agrees
$this->assertSame(['US-ENT','CANADA'],array_map(fn($r)=>$r['group_title'],$st->groups()));
}

public function test_reconcile_drops_vanished_pointers(): void {
$u=User::factory()->create(['email_verified_at'=>now()]);
$p=$this->providerWithStore($u);
Expand Down Expand Up @@ -156,7 +170,7 @@ public function test_refresh_inserts_new_channels_into_group_and_new_group_at_en
$this->actingAs($u)->postJson('/playlists',['name'=>'PL','providers'=>[$p->id]])->assertOk();
$pl=Playlist::first(); $st=new PlaylistStore($pl->id);
$ids=fn()=>array_map(fn($x)=>(int)$x['channel_id'],(new PlaylistStore($pl->id))->allForServe());
$this->assertSame([3,4,5,1,2],$ids()); // seed order = group_title, id
$this->assertSame([1,2,3,4,5],$ids()); // seed order follows GROUP order: US-ENT block (1,2) then CANADA (3,4,5)
// provider refresh: a NEW channel in an existing group (US C=6) + a NEW group with a channel (Sport 1=7)
$ps=new ProviderStore($p->id); $ps->begin();
$ps->upsertChannel(['name'=>'US C','url'=>'http://h/6.ts','group'=>'US-ENT'],'v2');
Expand All @@ -165,7 +179,7 @@ public function test_refresh_inserts_new_channels_into_group_and_new_group_at_en
$r=$st->insertNewFromProvider($p->id,$ps);
$this->assertSame(2,$r['channels_added']); $this->assertSame(1,$r['groups_added']);
// US C (6) joins the END of the US-ENT block (right after US B=2); the new group's Sport 1 (7) goes last
$this->assertSame([3,4,5,1,2,6,7],$ids());
$this->assertSame([1,2,6,3,4,5,7],$ids());
// idempotent: a second refresh with no new provider channels changes nothing
$this->assertSame(0,(new PlaylistStore($pl->id))->insertNewFromProvider($p->id,$ps)['channels_added']);
// a soft-deleted channel is NOT re-added by a later refresh
Expand Down