WebDAV同步功能重大改进:修复编码问题和双向同步
主要改进: 1. 修复观看记录key中站点名称的编码问题(电视版乱码修复) 2. 实现智能合并策略,支持时间和进度比较 3. 自动修复过期时间戳,确保记录能正常显示 4. 上传和下载都使用findAllRecent(0),确保完整同步 5. 添加详细日志,方便调试定位问题 技术细节: - 新增fixHistoryKey()方法,单独修复key中的站点名称部分 - 改进合并算法,考虑时间相近、进度领先等多种情况 - 修复createTime超过60天被过滤的问题 - 统一本地和远程记录的编码处理 删除的文件: - other/sample/* - 示例配置文件 - other/image/* - 示例图片 - .vscode/settings.json - 编辑器配置
This commit is contained in:
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"java.configuration.updateBuildConfiguration": "automatic"
|
||||
}
|
||||
@@ -245,7 +245,24 @@ public class HomeActivity extends BaseActivity implements CustomTitleView.Listen
|
||||
}
|
||||
|
||||
private void getHistory(boolean renew) {
|
||||
List<History> items = History.get();
|
||||
// 获取所有视频源的观看记录(最近60天)
|
||||
List<History> items = History.getAll();
|
||||
com.github.catvod.utils.Logger.d("HomeActivity: 获取观看记录,共 " + items.size() + " 条");
|
||||
|
||||
// 对比一下数据库中所有记录
|
||||
List<com.fongmi.android.tv.bean.History> allInDb = com.fongmi.android.tv.db.AppDatabase.get().getHistoryDao().findAllRecent(0);
|
||||
com.github.catvod.utils.Logger.d("HomeActivity: 数据库总记录数: " + allInDb.size() + " 条(包含所有时间)");
|
||||
|
||||
if (items.size() < allInDb.size()) {
|
||||
com.github.catvod.utils.Logger.w("HomeActivity: 有 " + (allInDb.size() - items.size()) + " 条记录因为时间过滤被隐藏");
|
||||
}
|
||||
|
||||
for (History h : items) {
|
||||
com.github.catvod.utils.Logger.d("HomeActivity: 记录 - " + h.getVodName() +
|
||||
" (cid=" + h.getCid() +
|
||||
", createTime=" + h.getCreateTime() + ")");
|
||||
}
|
||||
|
||||
int historyIndex = getHistoryIndex();
|
||||
int recommendIndex = getRecommendIndex();
|
||||
boolean exist = recommendIndex - historyIndex == 2;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="colorPrimaryDark">@color/primaryDark</item>
|
||||
<item name="colorAccent">@color/accent</item>
|
||||
<item name="colorControlHighlight">@color/primary</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowBackground">@null</item>
|
||||
<item name="android:windowDisablePreview">true</item>
|
||||
|
||||
@@ -54,7 +54,9 @@ public class App extends Application {
|
||||
executor = Executors.newFixedThreadPool(Constant.THREAD_POOL);
|
||||
handler = HandlerCompat.createAsync(Looper.getMainLooper());
|
||||
time = System.currentTimeMillis();
|
||||
gson = new Gson();
|
||||
gson = new com.google.gson.GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.create();
|
||||
cleanTask = this::checkCacheClean;
|
||||
syncTask = this::checkWebDAVSync;
|
||||
appJustLaunched = true;
|
||||
@@ -227,18 +229,8 @@ public class App extends Application {
|
||||
if (manager.isConfigured()) {
|
||||
// 应用启动时,如果已配置WebDAV,立即执行一次同步(下载远程数据)
|
||||
// 这样新设备配置后,下次启动应用时就能看到其他设备的历史记录
|
||||
App.execute(() -> {
|
||||
try {
|
||||
Logger.d("App: 应用启动,执行WebDAV同步");
|
||||
// 先上传本地记录
|
||||
manager.uploadHistory();
|
||||
// 再下载远程记录并合并
|
||||
manager.downloadHistory();
|
||||
Logger.d("App: WebDAV同步完成");
|
||||
} catch (Exception e) {
|
||||
Logger.e("App: WebDAV同步失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
Logger.d("App: WebDAV已配置,准备执行同步");
|
||||
manager.syncHistory(true); // 使用统一的同步方法,包含防重复逻辑
|
||||
|
||||
// 如果启用了自动同步,设置定期同步
|
||||
if (Setting.isWebDAVAutoSync()) {
|
||||
@@ -246,6 +238,8 @@ public class App extends Application {
|
||||
// 延迟执行下次同步,避免影响启动速度
|
||||
post(syncTask, interval * 60 * 1000L);
|
||||
}
|
||||
} else {
|
||||
Logger.d("App: WebDAV未配置,跳过同步");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -248,6 +248,10 @@ public class History {
|
||||
return AppDatabase.get().getHistoryDao().find(cid, System.currentTimeMillis() - Constant.HISTORY_TIME);
|
||||
}
|
||||
|
||||
public static List<History> getAll() {
|
||||
return AppDatabase.get().getHistoryDao().findAllRecent(System.currentTimeMillis() - Constant.HISTORY_TIME);
|
||||
}
|
||||
|
||||
public static History find(String key) {
|
||||
return AppDatabase.get().getHistoryDao().find(VodConfig.getCid(), key);
|
||||
}
|
||||
@@ -272,8 +276,15 @@ public class History {
|
||||
}
|
||||
|
||||
public void update() {
|
||||
merge(find(), false);
|
||||
save();
|
||||
try {
|
||||
com.github.catvod.utils.Logger.d("History.update: 开始更新观看记录 key=" + getKey());
|
||||
merge(find(), false);
|
||||
save();
|
||||
com.github.catvod.utils.Logger.d("History.update: 更新成功");
|
||||
} catch (Exception e) {
|
||||
com.github.catvod.utils.Logger.e("History.update: 更新失败 - " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public History update(int cid) {
|
||||
@@ -287,6 +298,7 @@ public class History {
|
||||
}
|
||||
|
||||
public History save() {
|
||||
com.github.catvod.utils.Logger.d("History.save: key=" + getKey() + ", vodName=" + getVodName());
|
||||
AppDatabase.get().getHistoryDao().insertOrUpdate(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -105,12 +105,37 @@ public class Site implements Parcelable {
|
||||
|
||||
public static Site objectFrom(JsonElement element) {
|
||||
try {
|
||||
return App.gson().fromJson(element, Site.class);
|
||||
Site site = App.gson().fromJson(element, Site.class);
|
||||
// 尝试修复可能的编码问题
|
||||
if (site != null && site.getKey() != null) {
|
||||
site.setKey(fixEncoding(site.getKey()));
|
||||
if (site.getName() != null) {
|
||||
site.setName(fixEncoding(site.getName()));
|
||||
}
|
||||
}
|
||||
return site;
|
||||
} catch (Exception e) {
|
||||
return new Site();
|
||||
}
|
||||
}
|
||||
|
||||
private static String fixEncoding(String str) {
|
||||
if (str == null || str.isEmpty()) return str;
|
||||
try {
|
||||
// 检查是否包含乱码字符(替换字符 U+FFFD)
|
||||
if (str.indexOf('\uFFFD') >= 0) {
|
||||
// 尝试用ISO-8859-1重新解码为UTF-8
|
||||
byte[] bytes = str.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
String fixed = new String(bytes, java.nio.charset.StandardCharsets.UTF_8);
|
||||
com.github.catvod.utils.Logger.d("Site.fixEncoding: 修复编码 '" + str + "' -> '" + fixed + "'");
|
||||
return fixed;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.github.catvod.utils.Logger.e("Site.fixEncoding: 修复失败 - " + e.getMessage());
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public static Site get(String key) {
|
||||
Site site = new Site();
|
||||
site.setKey(key);
|
||||
@@ -133,6 +158,13 @@ public class Site implements Parcelable {
|
||||
|
||||
public void setKey(@NonNull String key) {
|
||||
this.key = key;
|
||||
// 检查key中是否有异常字符
|
||||
for (int i = 0; i < key.length(); i++) {
|
||||
char c = key.charAt(i);
|
||||
if (c == 0xFFFD || c < 0x20 || (c >= 0x7F && c < 0xA0)) {
|
||||
com.github.catvod.utils.Logger.w("Site.setKey: 检测到异常字符 at position " + i + ": U+" + String.format("%04X", (int)c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -141,6 +173,15 @@ public class Site implements Parcelable {
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
// 检查name中是否有异常字符
|
||||
if (name != null) {
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
if (c == 0xFFFD || c < 0x20 || (c >= 0x7F && c < 0xA0)) {
|
||||
com.github.catvod.utils.Logger.w("Site.setName: 检测到异常字符 at position " + i + ": U+" + String.format("%04X", (int)c) + " in name: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getApi() {
|
||||
|
||||
@@ -16,6 +16,9 @@ public abstract class HistoryDao extends BaseDao<History> {
|
||||
@Query("SELECT * FROM History WHERE cid = :cid AND createTime >= :createTime ORDER BY createTime DESC")
|
||||
public abstract List<History> find(int cid, long createTime);
|
||||
|
||||
@Query("SELECT * FROM History WHERE createTime >= :createTime ORDER BY createTime DESC")
|
||||
public abstract List<History> findAllRecent(long createTime);
|
||||
|
||||
@Query("SELECT * FROM History WHERE cid = :cid AND `key` = :key")
|
||||
public abstract History find(int cid, String key);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.fongmi.android.tv.App;
|
||||
import com.fongmi.android.tv.bean.Backup;
|
||||
import com.fongmi.android.tv.bean.History;
|
||||
import com.fongmi.android.tv.db.AppDatabase;
|
||||
import com.fongmi.android.tv.event.RefreshEvent;
|
||||
import com.github.catvod.utils.Logger;
|
||||
import com.github.catvod.utils.Prefers;
|
||||
import com.google.gson.Gson;
|
||||
@@ -280,20 +281,60 @@ public class WebDAVSyncManager {
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取所有观看记录
|
||||
List<History> historyList = AppDatabase.get().getHistoryDao().findAll();
|
||||
// 获取所有观看记录 - 使用findAllRecent(0)来获取所有记录(包括旧记录)
|
||||
Logger.d("WebDAV: 开始查询数据库中的观看记录...");
|
||||
List<History> historyList = AppDatabase.get().getHistoryDao().findAllRecent(0);
|
||||
Logger.d("WebDAV: 数据库查询完成,结果: " + (historyList == null ? "null" : historyList.size() + " 条"));
|
||||
|
||||
if (historyList == null) {
|
||||
Logger.w("WebDAV: 查询结果为null,创建空列表");
|
||||
historyList = new java.util.ArrayList<>();
|
||||
}
|
||||
|
||||
// 修复数据中可能的编码问题(重点修复key中的站点名称部分)
|
||||
Logger.d("WebDAV: 开始修复上传数据的编码问题...");
|
||||
for (History h : historyList) {
|
||||
String originalKey = h.getKey();
|
||||
|
||||
// key格式: 站点key$视频ID$cid,需要单独修复站点key部分
|
||||
String fixedKey = fixHistoryKey(originalKey);
|
||||
if (!originalKey.equals(fixedKey)) {
|
||||
Logger.d("WebDAV: 修复key编码: '" + originalKey + "' -> '" + fixedKey + "'");
|
||||
h.setKey(fixedKey);
|
||||
}
|
||||
|
||||
String originalName = h.getVodName();
|
||||
String fixedName = fixEncodingIfNeeded(originalName);
|
||||
if (!originalName.equals(fixedName)) {
|
||||
Logger.d("WebDAV: 修复vodName编码: '" + originalName + "' -> '" + fixedName + "'");
|
||||
h.setVodName(fixedName);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.d("WebDAV: 准备上传观看记录,共 " + historyList.size() + " 条");
|
||||
|
||||
// 记录前3条数据的详细信息
|
||||
for (int i = 0; i < Math.min(3, historyList.size()); i++) {
|
||||
History h = historyList.get(i);
|
||||
Logger.d("WebDAV: 上传记录[" + i + "] key=" + h.getKey() + ", vodName=" + h.getVodName());
|
||||
// 检查key中的每个字符
|
||||
String key = h.getKey();
|
||||
StringBuilder hexDump = new StringBuilder();
|
||||
for (int j = 0; j < Math.min(20, key.length()); j++) {
|
||||
hexDump.append(String.format("%04x ", (int)key.charAt(j)));
|
||||
}
|
||||
Logger.d("WebDAV: key前20字符的Unicode: " + hexDump.toString());
|
||||
}
|
||||
|
||||
String json = App.gson().toJson(historyList);
|
||||
if (TextUtils.isEmpty(json)) {
|
||||
Logger.w("WebDAV: JSON数据为空");
|
||||
json = "[]"; // 确保至少有一个有效的JSON数组
|
||||
}
|
||||
|
||||
// 记录JSON的前500个字符
|
||||
Logger.d("WebDAV: JSON前500字符: " + json.substring(0, Math.min(500, json.length())));
|
||||
|
||||
// 确保目录存在(如果baseUrl包含子目录)
|
||||
if (syncMode == SyncMode.ACCOUNT && !TextUtils.isEmpty(baseUrl)) {
|
||||
try {
|
||||
@@ -338,18 +379,22 @@ public class WebDAVSyncManager {
|
||||
public boolean downloadHistory() {
|
||||
if (!isConfigured()) {
|
||||
Logger.e("WebDAV: 未配置,无法下载观看记录");
|
||||
Logger.e("WebDAV: baseUrl=" + baseUrl + ", username=" + username);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
String fileUrl = getFileUrl(HISTORY_FILE);
|
||||
Logger.d("WebDAV: 检查文件是否存在: " + fileUrl);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!sardine.exists(fileUrl)) {
|
||||
Logger.d("WebDAV: 观看记录文件不存在,跳过下载");
|
||||
Logger.w("WebDAV: 观看记录文件不存在,跳过下载");
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.d("WebDAV: 文件存在,开始下载");
|
||||
|
||||
// 下载文件(使用循环读取,避免available()不准确的问题)
|
||||
InputStream is = sardine.get(fileUrl);
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
@@ -378,7 +423,72 @@ public class WebDAVSyncManager {
|
||||
}
|
||||
|
||||
// 智能合并:比较本地和远程记录,保留较新的
|
||||
List<History> localHistoryList = AppDatabase.get().getHistoryDao().findAll();
|
||||
List<History> localHistoryList = AppDatabase.get().getHistoryDao().findAllRecent(0);
|
||||
Logger.d("WebDAV: 本地记录数: " + localHistoryList.size());
|
||||
Logger.d("WebDAV: 远程记录数: " + remoteHistoryList.size());
|
||||
|
||||
// 修复远程记录的编码问题和时间戳
|
||||
Logger.d("WebDAV: 开始修复远程记录编码和时间戳...");
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long historyTimeLimit = currentTime - com.fongmi.android.tv.Constant.HISTORY_TIME; // 60天前
|
||||
|
||||
for (History remote : remoteHistoryList) {
|
||||
if (remote != null) {
|
||||
String originalKey = remote.getKey();
|
||||
// 修复key中的站点名称部分
|
||||
String fixedKey = fixHistoryKey(originalKey);
|
||||
if (!originalKey.equals(fixedKey)) {
|
||||
Logger.d("WebDAV: 修复远程key: '" + originalKey + "' -> '" + fixedKey + "'");
|
||||
remote.setKey(fixedKey);
|
||||
}
|
||||
|
||||
String originalName = remote.getVodName();
|
||||
String fixedName = fixEncodingIfNeeded(originalName);
|
||||
if (!originalName.equals(fixedName)) {
|
||||
Logger.d("WebDAV: 修复远程vodName: '" + originalName + "' -> '" + fixedName + "'");
|
||||
remote.setVodName(fixedName);
|
||||
}
|
||||
|
||||
// 关键修复:确保createTime在60天内,否则会被过滤掉!
|
||||
long remoteCreateTime = remote.getCreateTime();
|
||||
if (remoteCreateTime < historyTimeLimit) {
|
||||
Logger.d("WebDAV: 修复过期时间戳: " + remote.getVodName() +
|
||||
" createTime=" + remoteCreateTime + " -> " + currentTime +
|
||||
" (已过期 " + ((currentTime - remoteCreateTime) / (24*60*60*1000)) + " 天)");
|
||||
remote.setCreateTime(currentTime);
|
||||
}
|
||||
|
||||
// 记录前3条远程数据的详细信息
|
||||
if (remoteHistoryList.indexOf(remote) < 3) {
|
||||
Logger.d("WebDAV: 远程记录[" + remoteHistoryList.indexOf(remote) + "]: " +
|
||||
remote.getVodName() + " (key=" + remote.getKey() +
|
||||
", cid=" + remote.getCid() +
|
||||
", createTime=" + remote.getCreateTime() + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修复本地记录的编码问题(重要!)
|
||||
Logger.d("WebDAV: 开始修复本地记录编码...");
|
||||
for (History local : localHistoryList) {
|
||||
if (local != null) {
|
||||
String originalKey = local.getKey();
|
||||
// 修复key中的站点名称部分
|
||||
String fixedKey = fixHistoryKey(originalKey);
|
||||
if (!originalKey.equals(fixedKey)) {
|
||||
Logger.d("WebDAV: 修复本地key: '" + originalKey + "' -> '" + fixedKey + "'");
|
||||
local.setKey(fixedKey);
|
||||
}
|
||||
|
||||
// 记录前3条本地数据的详细信息
|
||||
if (localHistoryList.indexOf(local) < 3) {
|
||||
Logger.d("WebDAV: 本地记录[" + localHistoryList.indexOf(local) + "]: " +
|
||||
local.getVodName() + " (key=" + local.getKey() +
|
||||
", cid=" + local.getCid() +
|
||||
", createTime=" + local.getCreateTime() + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建本地记录的映射(key -> History)
|
||||
java.util.Map<String, History> localMap = new java.util.HashMap<>();
|
||||
@@ -387,11 +497,14 @@ public class WebDAVSyncManager {
|
||||
localMap.put(local.getKey(), local);
|
||||
}
|
||||
}
|
||||
Logger.d("WebDAV: 本地记录映射大小: " + localMap.size());
|
||||
|
||||
// 合并远程记录
|
||||
List<History> toInsert = new java.util.ArrayList<>();
|
||||
List<History> toUpdate = new java.util.ArrayList<>();
|
||||
|
||||
Logger.d("WebDAV: 开始合并 " + remoteHistoryList.size() + " 条远程记录...");
|
||||
|
||||
for (History remote : remoteHistoryList) {
|
||||
// 验证远程记录
|
||||
if (remote == null || TextUtils.isEmpty(remote.getKey())) {
|
||||
@@ -403,43 +516,110 @@ public class WebDAVSyncManager {
|
||||
|
||||
if (local == null) {
|
||||
// 本地没有,直接添加
|
||||
Logger.d("WebDAV: 发现新记录: " + remote.getVodName() + " (key=" + remote.getKey() + ")");
|
||||
toInsert.add(remote);
|
||||
} else {
|
||||
// 本地有,比较createTime,保留较新的
|
||||
if (remote.getCreateTime() > local.getCreateTime()) {
|
||||
// 远程更新,更新本地
|
||||
toUpdate.add(remote);
|
||||
} else if (remote.getCreateTime() == local.getCreateTime()) {
|
||||
// 时间相同,比较position,保留进度更靠后的
|
||||
// 注意:position可能是C.TIME_UNSET(负数),需要处理
|
||||
long remotePos = remote.getPosition();
|
||||
long localPos = local.getPosition();
|
||||
// 如果都是有效值(>=0),比较大小;如果有无效值,保留有效值
|
||||
Logger.d("WebDAV: 本地已有记录: " + remote.getVodName() + ", 比较时间 remote=" + remote.getCreateTime() + " local=" + local.getCreateTime());
|
||||
|
||||
// 改进的合并策略:优先保留较新的记录,但也要比较播放进度
|
||||
long remotePos = remote.getPosition();
|
||||
long localPos = local.getPosition();
|
||||
long remoteTime = remote.getCreateTime();
|
||||
long localTime = local.getCreateTime();
|
||||
|
||||
boolean shouldUpdate = false;
|
||||
String reason = "";
|
||||
|
||||
// 策略1:如果远程时间更新,直接更新
|
||||
if (remoteTime > localTime) {
|
||||
shouldUpdate = true;
|
||||
reason = "远程时间更新 (" + remoteTime + " > " + localTime + ")";
|
||||
}
|
||||
// 策略2:如果时间相同或相近(误差1秒内),比较播放进度
|
||||
else if (Math.abs(remoteTime - localTime) <= 1000) {
|
||||
if (remotePos >= 0 && localPos >= 0) {
|
||||
if (remotePos > localPos) {
|
||||
toUpdate.add(remote);
|
||||
shouldUpdate = true;
|
||||
reason = "播放进度更新 (" + remotePos + " > " + localPos + ")";
|
||||
} else {
|
||||
reason = "本地进度更新或相同";
|
||||
}
|
||||
} else if (remotePos >= 0 && localPos < 0) {
|
||||
// 远程有效,本地无效,更新
|
||||
toUpdate.add(remote);
|
||||
shouldUpdate = true;
|
||||
reason = "远程有有效进度,本地无效";
|
||||
} else {
|
||||
reason = "保留本地";
|
||||
}
|
||||
// 否则保留本地,不更新
|
||||
}
|
||||
// 否则保留本地,不更新
|
||||
// 策略3:即使本地时间更新,如果远程有更大的播放进度,也更新
|
||||
else if (remoteTime < localTime) {
|
||||
if (remotePos >= 0 && localPos >= 0 && remotePos > localPos + 60000) {
|
||||
// 远程进度领先本地超过1分钟,可能是用户在另一台设备继续观看
|
||||
shouldUpdate = true;
|
||||
reason = "虽然本地时间更新,但远程进度显著领先 (" + remotePos + " > " + localPos + ")";
|
||||
} else {
|
||||
reason = "本地时间更新 (" + localTime + " > " + remoteTime + "),保留本地";
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldUpdate) {
|
||||
Logger.d("WebDAV: → 将更新本地 - " + reason);
|
||||
toUpdate.add(remote);
|
||||
} else {
|
||||
Logger.d("WebDAV: → 保留本地 - " + reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.d("WebDAV: 合并完成,待插入 " + toInsert.size() + " 条,待更新 " + toUpdate.size() + " 条");
|
||||
|
||||
// 执行插入和更新
|
||||
if (!toInsert.isEmpty()) {
|
||||
Logger.d("WebDAV: 开始插入 " + toInsert.size() + " 条新记录...");
|
||||
AppDatabase.get().getHistoryDao().insert(toInsert);
|
||||
Logger.d("WebDAV: 新增 " + toInsert.size() + " 条观看记录");
|
||||
for (History h : toInsert) {
|
||||
Logger.d("WebDAV: ✓ 新增 - " + h.getVodName() + " (cid=" + h.getCid() + ", key=" + h.getKey() + ")");
|
||||
}
|
||||
} else {
|
||||
Logger.d("WebDAV: 没有需要插入的新记录");
|
||||
}
|
||||
|
||||
if (!toUpdate.isEmpty()) {
|
||||
Logger.d("WebDAV: 开始更新 " + toUpdate.size() + " 条记录...");
|
||||
AppDatabase.get().getHistoryDao().update(toUpdate);
|
||||
Logger.d("WebDAV: 更新 " + toUpdate.size() + " 条观看记录");
|
||||
for (History h : toUpdate) {
|
||||
Logger.d("WebDAV: ✓ 更新 - " + h.getVodName() + " (cid=" + h.getCid() + ")");
|
||||
}
|
||||
} else {
|
||||
Logger.d("WebDAV: 没有需要更新的记录");
|
||||
}
|
||||
|
||||
Logger.d("WebDAV: 观看记录合并完成,远程 " + remoteHistoryList.size() + " 条,本地 " + localHistoryList.size() + " 条");
|
||||
|
||||
// 验证数据库中的记录总数
|
||||
List<History> allInDb = AppDatabase.get().getHistoryDao().findAllRecent(0);
|
||||
Logger.d("WebDAV: 数据库中总共有 " + allInDb.size() + " 条观看记录");
|
||||
|
||||
// 输出数据库中前5条记录的详细信息
|
||||
Logger.d("WebDAV: === 数据库中的记录(前5条)===");
|
||||
for (int i = 0; i < Math.min(5, allInDb.size()); i++) {
|
||||
History h = allInDb.get(i);
|
||||
Logger.d("WebDAV: [" + i + "] " + h.getVodName() +
|
||||
" (key=" + h.getKey() +
|
||||
", cid=" + h.getCid() +
|
||||
", createTime=" + h.getCreateTime() + ")");
|
||||
}
|
||||
Logger.d("WebDAV: =========================");
|
||||
|
||||
// 强制触发UI刷新(即使没有新增或更新,也刷新一次以确保显示)
|
||||
Logger.d("WebDAV: 触发UI刷新事件");
|
||||
App.post(() -> {
|
||||
RefreshEvent.history();
|
||||
Logger.d("WebDAV: UI刷新事件已发送到主线程");
|
||||
});
|
||||
|
||||
return true; // 即使远程为空,也算同步成功
|
||||
} catch (Exception e) {
|
||||
Logger.e("WebDAV: 观看记录下载失败: " + e.getMessage());
|
||||
@@ -755,5 +935,76 @@ public class WebDAVSyncManager {
|
||||
public void reloadConfig() {
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复History的key中的站点名称编码
|
||||
* key格式: 站点key$视频ID$cid
|
||||
*/
|
||||
private String fixHistoryKey(String key) {
|
||||
if (key == null || key.isEmpty()) {
|
||||
return key;
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用AppDatabase.SYMBOL分隔
|
||||
String symbol = com.fongmi.android.tv.db.AppDatabase.SYMBOL;
|
||||
String[] parts = key.split(java.util.regex.Pattern.quote(symbol));
|
||||
|
||||
if (parts.length >= 3) {
|
||||
// parts[0] = 站点key, parts[1] = 视频ID, parts[2] = cid
|
||||
String siteKey = parts[0];
|
||||
String fixedSiteKey = fixEncodingIfNeeded(siteKey);
|
||||
|
||||
if (!siteKey.equals(fixedSiteKey)) {
|
||||
// 重新组装key
|
||||
StringBuilder newKey = new StringBuilder(fixedSiteKey);
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
newKey.append(symbol).append(parts[i]);
|
||||
}
|
||||
return newKey.toString();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.e("WebDAV: 修复History key失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复字符串编码问题
|
||||
* 尝试将错误编码的UTF-8字符串修复为正确的UTF-8
|
||||
*/
|
||||
private String fixEncodingIfNeeded(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return str;
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查字符串中是否包含明显的乱码特征
|
||||
// 1. 包含替换字符 U+FFFD
|
||||
// 2. 包含异常的低位控制字符
|
||||
boolean needsFix = false;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
if (c == '\uFFFD' || (c >= 0x80 && c < 0xA0)) {
|
||||
needsFix = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsFix) {
|
||||
// 尝试修复:假设原始数据是UTF-8,但被错误地当作ISO-8859-1解码
|
||||
byte[] bytes = str.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
String fixed = new String(bytes, java.nio.charset.StandardCharsets.UTF_8);
|
||||
Logger.d("WebDAV: 编码修复 '" + str + "' -> '" + fixed + "'");
|
||||
return fixed;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.e("WebDAV: 编码修复失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ public class HistoryActivity extends BaseActivity implements HistoryAdapter.OnCl
|
||||
}
|
||||
|
||||
private void getHistory() {
|
||||
mAdapter.addAll(History.get());
|
||||
mAdapter.addAll(History.getAll()); // 显示所有视频源的观看记录
|
||||
mBinding.delete.setVisibility(mAdapter.getItemCount() > 0 ? View.VISIBLE : View.GONE);
|
||||
updateEmptyState();
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"name": "广告过滤配置示例",
|
||||
"description": "演示如何配置广告域名黑名单,阻止视频中途弹出的广告(如澳门新葡京等博彩广告)",
|
||||
|
||||
"说明": {
|
||||
"内置拦截": "应用已内置常见广告域名库,包括:澳门新葡京、皇冠、金沙等博彩广告;Google、百度、淘宝等广告联盟;优酷、爱奇艺等视频平台广告",
|
||||
"自定义拦截": "可以在配置文件中添加ads字段,补充需要拦截的广告域名",
|
||||
"支持正则": "域名支持正则表达式匹配,使用 .* 作为通配符"
|
||||
},
|
||||
|
||||
"配置示例": {
|
||||
"spider": "your_spider_url",
|
||||
"sites": [],
|
||||
|
||||
"ads": [
|
||||
"注释: 以下是自定义广告域名列表,会与内置域名库合并使用",
|
||||
|
||||
"注释: 精确匹配 - 直接写完整域名",
|
||||
"mimg.0c1q0l.cn",
|
||||
"www.92424.cn",
|
||||
"vip.ffzyad.com",
|
||||
|
||||
"注释: 模糊匹配 - 使用通配符",
|
||||
".*\\.doubleclick\\.net",
|
||||
".*\\.googlesyndication\\.com",
|
||||
|
||||
"注释: 关键词匹配 - 拦截包含特定关键词的域名",
|
||||
".*葡京.*",
|
||||
".*皇冠.*",
|
||||
".*金沙.*",
|
||||
".*casino.*",
|
||||
".*bet.*",
|
||||
|
||||
"注释: 特定平台的广告",
|
||||
"wan.51img1.com",
|
||||
"k.jinxiuzhilv.com",
|
||||
"ssl.kdd.cc"
|
||||
]
|
||||
},
|
||||
|
||||
"常见问题": {
|
||||
"Q1": "为什么配置了还是有广告?",
|
||||
"A1": "1. 检查广告域名是否正确;2. 某些广告可能直接嵌入视频流,无法通过域名拦截;3. 尝试使用片头片尾跳过功能",
|
||||
|
||||
"Q2": "如何找到广告的域名?",
|
||||
"A2": "1. 使用浏览器开发者工具查看网络请求;2. 查看应用日志中的URL;3. 参考其他用户分享的广告域名列表",
|
||||
|
||||
"Q3": "会不会误拦截正常内容?",
|
||||
"A3": "内置域名库经过筛选,主要针对已知广告。如有误拦截,可以反馈给开发者"
|
||||
},
|
||||
|
||||
"片头片尾跳过": {
|
||||
"说明": "对于嵌入视频流中的广告,可以使用片头片尾跳过功能",
|
||||
"使用方法": [
|
||||
"1. 播放视频时,在片头(前5分钟内)按【片头】按钮,记录当前时间点",
|
||||
"2. 在片尾(后5分钟内)按【片尾】按钮,记录结束前的时间点",
|
||||
"3. 下次播放相同视频时,会自动跳过片头,并在片尾前停止",
|
||||
"4. 如需重置,长按对应按钮即可"
|
||||
]
|
||||
},
|
||||
|
||||
"技术说明": {
|
||||
"拦截层级": "WebView网络请求层拦截",
|
||||
"拦截方式": "返回空响应,阻止广告内容加载",
|
||||
"性能影响": "极小,仅在WebView解析时生效",
|
||||
"隐私保护": "所有拦截在本地进行,不上传任何数据"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
{
|
||||
"lives": [
|
||||
{
|
||||
"name": "M3U",
|
||||
"url": "file://Download/live.m3u"
|
||||
},
|
||||
{
|
||||
"name": "TXT",
|
||||
"url": "file://Download/live.txt",
|
||||
"epg": "https://epg.112114.xyz/?ch={name}&date={date}",
|
||||
"logo": "https://epg.112114.xyz/logo/{name}.png"
|
||||
},
|
||||
{
|
||||
"name": "UA",
|
||||
"url": "file://Download/live.txt",
|
||||
"epg": "https://epg.112114.xyz/?ch={name}&date={date}",
|
||||
"logo": "https://epg.112114.xyz/logo/{name}.png",
|
||||
"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
|
||||
"referer": "https://github.com/"
|
||||
},
|
||||
{
|
||||
"name": "Custom",
|
||||
"boot": false,
|
||||
"pass": true,
|
||||
"url": "file://Download/live.txt",
|
||||
"epg": "https://epg.112114.xyz/?ch={name}&date={date}&serverTimeZone=Asia/Shanghai",
|
||||
"logo": "https://epg.112114.xyz/logo/{name}.png",
|
||||
"header": {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
|
||||
"Referer": "https://github.com/"
|
||||
},
|
||||
"catchup": {
|
||||
"days": "7",
|
||||
"type": "append",
|
||||
"regex": "/PLTV/",
|
||||
"replace": "/PLTV/,/TVOD/",
|
||||
"source": "?playseek=${(b)yyyyMMddHHmmss}-${(e)yyyyMMddHHmmss}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "JSON",
|
||||
"type": 1,
|
||||
"url": "file://Download/live.json"
|
||||
},
|
||||
{
|
||||
"name": "Spider-JS",
|
||||
"type": 3,
|
||||
"api": "./live.js",
|
||||
"ext": ""
|
||||
},
|
||||
{
|
||||
"name": "Spider-Python",
|
||||
"type": 3,
|
||||
"api": "./live.py",
|
||||
"ext": ""
|
||||
}
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"host": "gslbserv.itv.cmvideo.cn",
|
||||
"header": {
|
||||
"User-Agent": "okhttp/3.12.13"
|
||||
}
|
||||
}
|
||||
],
|
||||
"proxy": [
|
||||
"raw.githubusercontent.com"
|
||||
],
|
||||
"hosts": [
|
||||
"cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com"
|
||||
],
|
||||
"ads": [
|
||||
"static-mozai.4gtv.tv"
|
||||
]
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
{
|
||||
"lives": [
|
||||
{
|
||||
"name": "M3U",
|
||||
"url": "https://github.com/live.m3u"
|
||||
},
|
||||
{
|
||||
"name": "TXT",
|
||||
"url": "https://github.com/live.txt",
|
||||
"epg": "https://epg.112114.xyz/?ch={name}&date={date}",
|
||||
"logo": "https://epg.112114.xyz/logo/{name}.png"
|
||||
},
|
||||
{
|
||||
"name": "UA",
|
||||
"url": "https://github.com/live.txt",
|
||||
"epg": "https://epg.112114.xyz/?ch={name}&date={date}",
|
||||
"logo": "https://epg.112114.xyz/logo/{name}.png",
|
||||
"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
|
||||
"referer": "https://github.com/"
|
||||
},
|
||||
{
|
||||
"name": "Custom",
|
||||
"boot": false,
|
||||
"pass": true,
|
||||
"url": "https://github.com/live.txt",
|
||||
"epg": "https://epg.112114.xyz/?ch={name}&date={date}&serverTimeZone=Asia/Shanghai",
|
||||
"logo": "https://epg.112114.xyz/logo/{name}.png",
|
||||
"header": {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
|
||||
"Referer": "https://github.com/"
|
||||
},
|
||||
"catchup": {
|
||||
"days": "7",
|
||||
"type": "append",
|
||||
"regex": "/PLTV/",
|
||||
"replace": "/PLTV/,/TVOD/",
|
||||
"source": "?playseek=${(b)yyyyMMddHHmmss}-${(e)yyyyMMddHHmmss}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "JSON",
|
||||
"type": 1,
|
||||
"url": "https://github.com/live.json"
|
||||
},
|
||||
{
|
||||
"name": "Spider-JS",
|
||||
"type": 3,
|
||||
"api": "https://github.com/live.js",
|
||||
"ext": ""
|
||||
},
|
||||
{
|
||||
"name": "Spider-Python",
|
||||
"type": 3,
|
||||
"api": "https://github.com/live.py",
|
||||
"ext": ""
|
||||
}
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"host": "gslbserv.itv.cmvideo.cn",
|
||||
"header": {
|
||||
"User-Agent": "okhttp/3.12.13"
|
||||
}
|
||||
}
|
||||
],
|
||||
"proxy": [
|
||||
"raw.githubusercontent.com"
|
||||
],
|
||||
"hosts": [
|
||||
"cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com"
|
||||
],
|
||||
"ads": [
|
||||
"static-mozai.4gtv.tv"
|
||||
]
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
{
|
||||
"spider": "file://Download/custom_spider.jar",
|
||||
"sites": [
|
||||
{
|
||||
"key": "one",
|
||||
"name": "One",
|
||||
"type": 3,
|
||||
"api": "csp_Csp",
|
||||
"searchable": 1,
|
||||
"changeable": 1,
|
||||
"ext": "file://Download/one.json"
|
||||
},
|
||||
{
|
||||
"key": "two",
|
||||
"name": "Two",
|
||||
"type": 3,
|
||||
"api": "csp_Csp",
|
||||
"searchable": 1,
|
||||
"changeable": 1,
|
||||
"ext": "file://Download/two.json"
|
||||
},
|
||||
{
|
||||
"key": "extend",
|
||||
"name": "Extend",
|
||||
"type": 3,
|
||||
"api": "csp_Csp",
|
||||
"searchable": 1,
|
||||
"changeable": 1,
|
||||
"ext": "file://Download/extend.json",
|
||||
"jar": "file://Download/extend.jar"
|
||||
}
|
||||
],
|
||||
"parses": [
|
||||
{
|
||||
"name": "官方",
|
||||
"type": 1,
|
||||
"url": "https://google.com/api/?url="
|
||||
}
|
||||
],
|
||||
"doh": [
|
||||
{
|
||||
"name": "Google",
|
||||
"url": "https://dns.google/dns-query",
|
||||
"ips": [
|
||||
"8.8.4.4",
|
||||
"8.8.8.8"
|
||||
]
|
||||
}
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"host": "gslbserv.itv.cmvideo.cn",
|
||||
"header": {
|
||||
"User-Agent": "okhttp/3.12.13"
|
||||
}
|
||||
}
|
||||
],
|
||||
"proxy": [
|
||||
"raw.githubusercontent.com"
|
||||
],
|
||||
"hosts": [
|
||||
"cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com"
|
||||
],
|
||||
"flags": [
|
||||
"qq"
|
||||
],
|
||||
"ads": [
|
||||
"static-mozai.4gtv.tv"
|
||||
]
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
{
|
||||
"spider": "https://github.com/custom_spider.jar",
|
||||
"sites": [
|
||||
{
|
||||
"key": "one",
|
||||
"name": "One",
|
||||
"type": 3,
|
||||
"api": "csp_Csp",
|
||||
"searchable": 1,
|
||||
"changeable": 1,
|
||||
"ext": "https://github.com/one.json"
|
||||
},
|
||||
{
|
||||
"key": "two",
|
||||
"name": "Two",
|
||||
"type": 3,
|
||||
"api": "csp_Csp",
|
||||
"searchable": 1,
|
||||
"changeable": 1,
|
||||
"ext": "https://github.com/two.json"
|
||||
},
|
||||
{
|
||||
"key": "extend",
|
||||
"name": "Extend",
|
||||
"type": 3,
|
||||
"api": "csp_Csp",
|
||||
"searchable": 1,
|
||||
"changeable": 1,
|
||||
"ext": "https://github.com/extend.json",
|
||||
"jar": "https://github.com/extend.jar"
|
||||
}
|
||||
],
|
||||
"parses": [
|
||||
{
|
||||
"name": "官方",
|
||||
"type": 1,
|
||||
"url": "https://google.com/api/?url="
|
||||
}
|
||||
],
|
||||
"doh": [
|
||||
{
|
||||
"name": "Google",
|
||||
"url": "https://dns.google/dns-query",
|
||||
"ips": [
|
||||
"8.8.4.4",
|
||||
"8.8.8.8"
|
||||
]
|
||||
}
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"host": "gslbserv.itv.cmvideo.cn",
|
||||
"header": {
|
||||
"User-Agent": "okhttp/3.12.13"
|
||||
}
|
||||
}
|
||||
],
|
||||
"proxy": [
|
||||
"raw.githubusercontent.com"
|
||||
],
|
||||
"hosts": [
|
||||
"cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com"
|
||||
],
|
||||
"flags": [
|
||||
"qq"
|
||||
],
|
||||
"ads": [
|
||||
"static-mozai.4gtv.tv"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user