Skip to content

Commit 73fcc01

Browse files
StreamingCommunity Multilingual, Database & Backup Refactoring, "Continue Watching" Fix, and UI Refinement
StreamingCommunity & Extractor thanks to Or-Cr: - Domain Replacement: Global update of all references from streamingcommunity.so to streamingcommunity.tv. - Multilingual Support: Implemented IT/EN management for StreamingCommunity with dynamic instances, based on specific API URLs and tailored HTTP headers (Accept-Language, Cookie). - Content Loading Fix: Resolved 404 errors and empty sections by parsing Inertia JSON payloads from archive pages (titles, movies, tv_shows). - Vixcloud Audio Fix: Implemented "Aggressive Manifest Patching" to filter and force the correct audio track (IT/EN) in HLS manifests (.m3u8), bypassing ExoPlayer limitations. - NetworkOnMainThread Fix: Moved domain resolution logic inside the logo getter to prevent crashes during UI navigation. Database & Backup System thanks to Or-Cr: - Critical Singleton Fix: Introduced AppDatabase.resetInstance() to prevent cross-provider data contamination (avoiding overwrites between different .db files). - Backup Integrity (Smart Merge): Replaced the REPLACE strategy with custom merge() logic to preserve rich metadata (overviews, ratings, banners) during restoration. - Extended Support: Added Season entity and dynamic providers (TMDb it/en) support to the export/import system. - Audit & Logging: Introduced DatabaseVerify and BackupVerify tags for granular real-time monitoring of database operations. - Sanitization: Implemented database filename cleaning to properly handle special characters in provider names. Interface and "Continue Watching" thanks to Or-Cr: - State Synchronization: Resolved TV Show desync; the parent isWatching state now correctly depends on the actual existence of watch history across episodes. - UI Refinement & Fixes: Fixed compilation errors related to the PlayerResize enum and syntax typos in option dialogs. Other Changes: - Altadefinizione01: Updated provider URL. - CB01: Updated provider URL.
1 parent b91e4ca commit 73fcc01

19 files changed

+840
-265
lines changed

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ android {
3030
applicationId "com.streamflixreborn.streamflix"
3131
minSdk 21
3232
targetSdk 35
33-
versionCode 113
34-
versionName "1.7.84"
33+
versionCode 114
34+
versionName "1.7.85"
3535

3636
buildConfigField "String", "APP_LAYOUT", "\"${properties.getProperty("APP_LAYOUT")}\""
3737
buildConfigField "String", "TMDB_API_KEY", "\"${properties.getProperty("TMDB_API_KEY")}\""

app/src/main/java/com/streamflixreborn/streamflix/backup/BackupRestoreManager.kt

Lines changed: 137 additions & 70 deletions
Large diffs are not rendered by default.

app/src/main/java/com/streamflixreborn/streamflix/database/AppDatabase.kt

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,34 +43,58 @@ abstract class AppDatabase : RoomDatabase() {
4343
@Volatile
4444
private var INSTANCE: AppDatabase? = null
4545

46+
private fun sanitizeProviderName(name: String): String {
47+
// Rimuove caratteri non validi per i nomi dei file DB,
48+
// come spazi, parentesi, e li converte in lowercase.
49+
return name.lowercase()
50+
.replace("[^a-z0-9]".toRegex(), "_")
51+
.replace("__+".toRegex(), "_") // Sostituisce doppie underscore con una singola
52+
.trim('_') // Rimuove underscore iniziale/finale
53+
}
54+
4655
fun setup(context: Context) {
4756
if (UserPreferences.currentProvider == null) return
4857

4958
synchronized(this) {
50-
buildDatabase(UserPreferences.currentProvider!!.name, context).also { INSTANCE = it }
59+
INSTANCE?.close() // Chiudi connessioni esistenti
60+
INSTANCE = buildDatabase(UserPreferences.currentProvider!!.name, context)
5161
}
5262
}
5363

54-
fun getInstance(context: Context) =
55-
INSTANCE ?: synchronized(this) {
56-
buildDatabase(UserPreferences.currentProvider!!.name, context).also { INSTANCE = it }
64+
fun getInstance(context: Context): AppDatabase {
65+
return INSTANCE ?: synchronized(this) {
66+
val instance = buildDatabase(UserPreferences.currentProvider!!.name, context)
67+
INSTANCE = instance
68+
instance
5769
}
70+
}
71+
72+
// Metodo per forzare il cambio di database quando cambia il provider
73+
fun resetInstance() {
74+
synchronized(this) {
75+
INSTANCE?.close()
76+
INSTANCE = null
77+
}
78+
}
79+
5880
fun getInstanceForProvider(providerName: String, context: Context): AppDatabase {
5981
return buildDatabase(providerName, context)
6082
}
6183

62-
private fun buildDatabase(providerName: String, context: Context) =
63-
Room.databaseBuilder(
84+
private fun buildDatabase(providerName: String, context: Context): AppDatabase {
85+
val sanitizedName = sanitizeProviderName(providerName)
86+
return Room.databaseBuilder(
6487
context = context.applicationContext,
6588
klass = AppDatabase::class.java,
66-
name = "${providerName.lowercase()}.db"
89+
name = "$sanitizedName.db"
6790
)
6891
.allowMainThreadQueries()
6992
.addMigrations(MIGRATION_1_2)
7093
.addMigrations(MIGRATION_2_3)
7194
.addMigrations(MIGRATION_3_4)
7295
.addMigrations(MIGRATION_4_5)
7396
.build()
97+
}
7498

7599

76100
private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
@@ -89,25 +113,21 @@ abstract class AppDatabase : RoomDatabase() {
89113

90114
private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
91115
override fun migrate(db: SupportSQLiteDatabase) {
92-
// Change episodes.title to Nullable
93116
db.execSQL("CREATE TABLE `episodes_temp` (`id` TEXT NOT NULL, `number` INTEGER NOT NULL, `title` TEXT, `poster` TEXT, `tvShow` TEXT, `season` TEXT, `released` TEXT, `isWatched` INTEGER NOT NULL, `watchedDate` TEXT, `lastEngagementTimeUtcMillis` INTEGER, `lastPlaybackPositionMillis` INTEGER, `durationMillis` INTEGER, PRIMARY KEY(`id`))")
94117
db.execSQL("INSERT INTO episodes_temp SELECT * FROM episodes")
95118
db.execSQL("DROP TABLE episodes")
96119
db.execSQL("ALTER TABLE episodes_temp RENAME TO episodes")
97120

98-
// Change movies.overview to Nullable
99121
db.execSQL("CREATE TABLE `movies_temp` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `overview` TEXT, `runtime` INTEGER, `trailer` TEXT, `quality` TEXT, `rating` REAL, `poster` TEXT, `banner` TEXT, `released` TEXT, `isFavorite` INTEGER NOT NULL, `isWatched` INTEGER NOT NULL, `watchedDate` TEXT, `lastEngagementTimeUtcMillis` INTEGER, `lastPlaybackPositionMillis` INTEGER, `durationMillis` INTEGER, PRIMARY KEY(`id`))")
100122
db.execSQL("INSERT INTO movies_temp SELECT * FROM movies")
101123
db.execSQL("DROP TABLE movies")
102124
db.execSQL("ALTER TABLE movies_temp RENAME TO movies")
103125

104-
// Change seasons.title, seasons.poster to Nullable
105126
db.execSQL("CREATE TABLE `seasons_temp` (`id` TEXT NOT NULL, `number` INTEGER NOT NULL, `title` TEXT, `poster` TEXT, `tvShow` TEXT, PRIMARY KEY(`id`))")
106127
db.execSQL("INSERT INTO seasons_temp SELECT * FROM seasons")
107128
db.execSQL("DROP TABLE seasons")
108129
db.execSQL("ALTER TABLE seasons_temp RENAME TO seasons")
109130

110-
// Change tv_shows.overview to Nullable
111131
db.execSQL("CREATE TABLE `tv_shows_temp` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `overview` TEXT, `runtime` INTEGER, `trailer` TEXT, `quality` TEXT, `rating` REAL, `poster` TEXT, `banner` TEXT, `released` TEXT, `isFavorite` INTEGER NOT NULL, PRIMARY KEY(`id`))")
112132
db.execSQL("INSERT INTO tv_shows_temp SELECT * FROM tv_shows")
113133
db.execSQL("DROP TABLE tv_shows")

app/src/main/java/com/streamflixreborn/streamflix/database/dao/EpisodeDao.kt

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
package com.streamflixreborn.streamflix.database.dao
22

3+
import android.util.Log
34
import androidx.room.Dao
45
import androidx.room.Insert
56
import androidx.room.OnConflictStrategy
67
import androidx.room.Query
78
import androidx.room.Update
89
import com.streamflixreborn.streamflix.models.Episode
910
import kotlinx.coroutines.flow.Flow
11+
import androidx.room.Transaction
12+
import com.streamflixreborn.streamflix.utils.UserPreferences
1013

1114
@Dao
1215
interface EpisodeDao {
1316

1417
@Query("SELECT * FROM episodes")
15-
fun getAllForBackup(): List<Episode> // NUOVO: Per l'esportazione
18+
fun getAllForBackup(): List<Episode>
1619

1720
@Query(
1821
"""
@@ -57,10 +60,9 @@ interface EpisodeDao {
5760
@Query("SELECT * FROM episodes WHERE id IN (:ids)")
5861
fun getByIdsAsFlow(ids: List<String>): Flow<List<Episode>>
5962

60-
// Nota: Le query seguenti usano `tvShow = :tvShowId` e `season = :seasonId`.
61-
// Questo implica che i campi `tvShow` e `season` in Episode.kt potrebbero essere
62-
// usati per memorizzare ID o necessitano di TypeConverter specifici.
63-
// La corretta gestione di queste relazioni è cruciale per il backup/ripristino.
63+
@Query("SELECT COUNT(id) > 0 FROM episodes WHERE tvShow = :tvShowId AND lastEngagementTimeUtcMillis IS NOT NULL")
64+
fun hasAnyWatchHistoryForTvShow(tvShowId: String): Boolean
65+
6466
@Query("SELECT * FROM episodes WHERE tvShow = :tvShowId ORDER BY season, number")
6567
fun getByTvShowId(tvShowId: String): List<Episode>
6668

@@ -77,7 +79,7 @@ interface EpisodeDao {
7779
fun insert(episode: Episode)
7880

7981
@Insert(onConflict = OnConflictStrategy.REPLACE)
80-
fun insertAll(episodes: List<Episode>) // Esistente, corretto per l'importazione
82+
fun insertAll(episodes: List<Episode>)
8183

8284
@Query("SELECT * FROM episodes WHERE tvShow = :tvShowId")
8385
fun getEpisodesByTvShowId(tvShowId: String): List<Episode>
@@ -86,17 +88,21 @@ interface EpisodeDao {
8688
fun update(episode: Episode)
8789

8890
@Query("DELETE FROM episodes")
89-
fun deleteAll() // NUOVO: Per l'importazione
90-
91-
fun save(episode: Episode) = getById(episode.id)
92-
?.let {
93-
// Per preservare l'ID esistente e unire i campi rilevanti.
94-
// La logica di merge effettiva è nel modello Episode.
95-
val mergedEpisode = it.copy() // Crea una copia dell'entità dal DB
96-
mergedEpisode.merge(episode) // Unisci i dati dall'episodio in input
97-
update(mergedEpisode) // Aggiorna l'entità unita
91+
fun deleteAll()
92+
93+
@Transaction
94+
fun save(episode: Episode) {
95+
val provider = UserPreferences.currentProvider?.name ?: "Unknown"
96+
val existing = getById(episode.id)
97+
if (existing != null) {
98+
existing.merge(episode)
99+
update(existing)
100+
Log.d("DatabaseVerify", "[$provider] REAL-TIME UPDATE Episode: ${existing.title} (Watched: ${existing.isWatched}, Hist: ${existing.watchHistory != null})")
101+
} else {
102+
insert(episode)
103+
Log.d("DatabaseVerify", "[$provider] REAL-TIME INSERT Episode: ${episode.id} (Watched: ${episode.isWatched})")
98104
}
99-
?: insert(episode)
105+
}
100106

101107
@Query(
102108
"""

app/src/main/java/com/streamflixreborn/streamflix/database/dao/MovieDao.kt

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
package com.streamflixreborn.streamflix.database.dao
22

3+
import android.util.Log
34
import androidx.room.Dao
45
import androidx.room.Insert
56
import androidx.room.OnConflictStrategy
67
import androidx.room.Query
78
import androidx.room.Update
89
import com.streamflixreborn.streamflix.models.Movie
910
import kotlinx.coroutines.flow.Flow
11+
import androidx.room.Transaction
12+
import com.streamflixreborn.streamflix.utils.UserPreferences
1013

1114
@Dao
1215
interface MovieDao {
1316

1417
@Query("SELECT * FROM movies")
15-
fun getAll(): List<Movie> // NUOVO: Per l'esportazione
18+
fun getAll(): List<Movie>
1619

1720
@Query("SELECT * FROM movies WHERE id = :id")
1821
fun getById(id: String): Movie?
@@ -33,17 +36,35 @@ interface MovieDao {
3336
fun insert(movie: Movie)
3437

3538
@Insert(onConflict = OnConflictStrategy.REPLACE)
36-
fun insertAll(movies: List<Movie>) // NUOVO: Per l'importazione
39+
fun insertAll(movies: List<Movie>)
3740

3841
@Update
3942
fun update(movie: Movie)
4043

4144
@Query("DELETE FROM movies")
42-
fun deleteAll() // NUOVO: Per l'importazione
45+
fun deleteAll()
46+
47+
@Transaction
48+
fun save(movie: Movie) {
49+
val provider = UserPreferences.currentProvider?.name ?: "Unknown"
50+
val existing = getById(movie.id)
51+
if (existing != null) {
52+
val merged = existing.merge(movie)
53+
update(merged)
54+
Log.d("DatabaseVerify", "[$provider] REAL-TIME UPDATE Movie: ${merged.title} (Fav: ${merged.isFavorite}, Watched: ${merged.isWatched})")
55+
} else {
56+
insert(movie)
57+
Log.d("DatabaseVerify", "[$provider] REAL-TIME INSERT Movie: ${movie.title} (Fav: ${movie.isFavorite})")
58+
}
59+
}
60+
61+
@Transaction
62+
fun setFavoriteWithLog(id: String, favorite: Boolean) {
63+
val provider = UserPreferences.currentProvider?.name ?: "Unknown"
64+
setFavorite(id, favorite)
65+
Log.d("DatabaseVerify", "[$provider] REAL-TIME Favorite Toggled: ID $id -> $favorite")
66+
}
4367

44-
fun save(movie: Movie) = getById(movie.id)
45-
?.let { update(movie.copy(id = it.id)) } // Assicurati che l'update usi l'ID corretto
46-
?: insert(movie)
4768
@Query("UPDATE movies SET isFavorite = :favorite WHERE id = :id")
4869
fun setFavorite(id: String, favorite: Boolean)
4970

app/src/main/java/com/streamflixreborn/streamflix/database/dao/SeasonDao.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import androidx.room.Dao
44
import androidx.room.Insert
55
import androidx.room.OnConflictStrategy
66
import androidx.room.Query
7+
import androidx.room.Transaction
78
import com.streamflixreborn.streamflix.models.Season
89
import kotlinx.coroutines.flow.Flow
910

1011
@Dao
1112
interface SeasonDao {
1213

14+
@Query("SELECT * FROM seasons")
15+
fun getAllForBackup(): List<Season>
16+
1317
@Query("SELECT * FROM seasons WHERE id = :id")
1418
fun getById(id: String): Season?
1519

@@ -19,6 +23,19 @@ interface SeasonDao {
1923
@Query("SELECT * FROM seasons WHERE tvShow = :tvShowId")
2024
fun getByTvShowIdAsFlow(tvShowId: String): Flow<List<Season>>
2125

26+
@Insert(onConflict = OnConflictStrategy.REPLACE)
27+
fun insert(season: Season)
28+
2229
@Insert(onConflict = OnConflictStrategy.REPLACE)
2330
fun insertAll(seasons: List<Season>)
24-
}
31+
32+
@Query("DELETE FROM seasons")
33+
fun deleteAll()
34+
35+
@Transaction
36+
fun saveAll(seasons: List<Season>) {
37+
// La logica di "save" per le stagioni è meno critica di Movie/TvShow, usiamo REPLACE
38+
// per salvare tutte le stagioni di un provider in blocco.
39+
insertAll(seasons)
40+
}
41+
}

app/src/main/java/com/streamflixreborn/streamflix/database/dao/TvShowDao.kt

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
package com.streamflixreborn.streamflix.database.dao
22

3+
import android.util.Log
34
import androidx.room.Dao
45
import androidx.room.Insert
56
import androidx.room.OnConflictStrategy
67
import androidx.room.Query
78
import androidx.room.Update
89
import com.streamflixreborn.streamflix.models.TvShow
910
import kotlinx.coroutines.flow.Flow
11+
import androidx.room.Transaction
12+
import com.streamflixreborn.streamflix.utils.UserPreferences
1013

1114
@Dao
1215
interface TvShowDao {
1316

1417
@Query("SELECT * FROM tv_shows")
15-
fun getAllForBackup(): List<TvShow> // NUOVO: Sincrono per l'esportazione
18+
fun getAllForBackup(): List<TvShow>
1619

1720
@Query("SELECT * FROM tv_shows WHERE id = :id")
1821
fun getById(id: String): TvShow?
@@ -29,14 +32,14 @@ interface TvShowDao {
2932
@Insert(onConflict = OnConflictStrategy.REPLACE)
3033
fun insert(tvShow: TvShow)
3134

32-
@Update
35+
@Update(onConflict = OnConflictStrategy.REPLACE)
3336
fun update(tvShow: TvShow)
3437

35-
@Insert(onConflict = OnConflictStrategy.REPLACE) // MODIFICATO: OnConflictStrategy e rimozione suspend (se non necessario)
38+
@Insert(onConflict = OnConflictStrategy.REPLACE)
3639
fun insertAll(tvShows: List<TvShow>)
3740

3841
@Query("SELECT * FROM tv_shows")
39-
fun getAll(): Flow<List<TvShow>> // Esistente, mantenuto
42+
fun getAll(): Flow<List<TvShow>>
4043

4144
@Query("SELECT * FROM tv_shows WHERE poster IS NULL or poster = ''")
4245
suspend fun getAllWithNullPoster(): List<TvShow>
@@ -48,11 +51,28 @@ interface TvShowDao {
4851
suspend fun searchTvShows(query: String, limit: Int, offset: Int): List<TvShow>
4952

5053
@Query("DELETE FROM tv_shows")
51-
fun deleteAll() // NUOVO: Per l'importazione
52-
53-
fun save(tvShow: TvShow) = getById(tvShow.id)
54-
?.let { update(tvShow.copy(id = it.id)) } // Assicurati che l'update usi l'ID corretto
55-
?: insert(tvShow)
54+
fun deleteAll()
55+
56+
@Transaction
57+
fun save(tvShow: TvShow) {
58+
val provider = UserPreferences.currentProvider?.name ?: "Unknown"
59+
val existing = getById(tvShow.id)
60+
if (existing != null) {
61+
val merged = existing.merge(tvShow)
62+
update(merged)
63+
Log.d("DatabaseVerify", "[$provider] REAL-TIME UPDATE TV Show: ${merged.title} (Fav: ${merged.isFavorite}, Watching: ${merged.isWatching})")
64+
} else {
65+
insert(tvShow)
66+
Log.d("DatabaseVerify", "[$provider] REAL-TIME INSERT TV Show: ${tvShow.title} (Fav: ${tvShow.isFavorite})")
67+
}
68+
}
69+
70+
@Transaction
71+
fun setFavoriteWithLog(id: String, favorite: Boolean) {
72+
val provider = UserPreferences.currentProvider?.name ?: "Unknown"
73+
setFavorite(id, favorite)
74+
Log.d("DatabaseVerify", "[$provider] REAL-TIME Favorite Toggled: ID $id -> $favorite")
75+
}
5676

5777
@Query("UPDATE tv_shows SET isFavorite = :favorite WHERE id = :id")
5878
fun setFavorite(id: String, favorite: Boolean)

0 commit comments

Comments
 (0)