mirror of
https://github.com/sartoopjj/thefeed.git
synced 2026-05-18 06:44:34 +03:00
feat: enhance client probing mechanism and improve resolver scan logging
This commit is contained in:
@@ -25,7 +25,6 @@ class MainActivity : ComponentActivity() {
|
||||
private lateinit var webView: WebView
|
||||
private lateinit var txtStatus: TextView
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private var probeAttempt = 0
|
||||
|
||||
private val notificationPermissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
@@ -41,7 +40,6 @@ class MainActivity : ComponentActivity() {
|
||||
requestNotificationPermission()
|
||||
configureWebView()
|
||||
startThefeedService()
|
||||
probeAttempt = 0
|
||||
waitForServerThenLoad()
|
||||
}
|
||||
|
||||
@@ -84,7 +82,6 @@ class MainActivity : ComponentActivity() {
|
||||
) {
|
||||
// Server was reachable during probe but dropped connection — retry probe cycle
|
||||
if (request?.isForMainFrame == true) {
|
||||
probeAttempt = 0
|
||||
setStatus("Reconnecting...")
|
||||
handler.postDelayed({ waitForServerThenLoad() }, RETRY_DELAY_MS)
|
||||
}
|
||||
@@ -102,57 +99,48 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls the server in a background thread until it responds with HTTP 200 (or any
|
||||
* response — a TCP connection refused is what we're avoiding). Only then hands the
|
||||
* URL to WebView, ensuring it never shows a browser error page on startup.
|
||||
* Polls SharedPreferences for the port on every attempt, then probes the URL.
|
||||
* This handles force-kill restarts where the service picks a new port:
|
||||
* the loop follows the port change automatically instead of hammering a stale one.
|
||||
*/
|
||||
private fun waitForServerThenLoad() {
|
||||
val port = getCurrentPort()
|
||||
if (port <= 0) {
|
||||
if (probeAttempt < MAX_PROBE_ATTEMPTS) {
|
||||
probeAttempt++
|
||||
setStatus("Waiting for service... ($probeAttempt/$MAX_PROBE_ATTEMPTS)")
|
||||
handler.postDelayed({ waitForServerThenLoad() }, PROBE_INTERVAL_MS)
|
||||
} else {
|
||||
setStatus("Service unavailable. Restart the app.")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val url = "http://127.0.0.1:$port"
|
||||
setStatus("Connecting...")
|
||||
|
||||
setStatus("Waiting for service...")
|
||||
Thread {
|
||||
var ready = false
|
||||
repeat(MAX_PROBE_ATTEMPTS) { attempt ->
|
||||
if (ready) return@repeat
|
||||
var lastPort = -1
|
||||
for (attempt in 1..MAX_PROBE_ATTEMPTS) {
|
||||
val port = getCurrentPort()
|
||||
if (port <= 0) {
|
||||
handler.post { setStatus("Waiting for service... ($attempt/$MAX_PROBE_ATTEMPTS)") }
|
||||
Thread.sleep(PROBE_INTERVAL_MS)
|
||||
continue
|
||||
}
|
||||
if (port != lastPort) {
|
||||
// Service restarted with a new port — reset and start fresh
|
||||
lastPort = port
|
||||
handler.post { setStatus("Connecting...") }
|
||||
}
|
||||
try {
|
||||
val conn = URL(url).openConnection() as HttpURLConnection
|
||||
val conn = URL("http://127.0.0.1:$port").openConnection() as HttpURLConnection
|
||||
conn.connectTimeout = PROBE_TIMEOUT_MS.toInt()
|
||||
conn.readTimeout = PROBE_TIMEOUT_MS.toInt()
|
||||
conn.requestMethod = "GET"
|
||||
val code = conn.responseCode
|
||||
conn.disconnect()
|
||||
if (code > 0) { // any HTTP response means the server is up
|
||||
if (code > 0) {
|
||||
ready = true
|
||||
return@repeat
|
||||
val url = "http://127.0.0.1:$port"
|
||||
handler.post { setStatus(""); webView.loadUrl(url) }
|
||||
return@Thread
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// Connection refused or timeout — server not ready yet
|
||||
// Connection refused — not ready yet
|
||||
}
|
||||
handler.post { setStatus("Waiting for server... ($attempt/$MAX_PROBE_ATTEMPTS)") }
|
||||
Thread.sleep(PROBE_INTERVAL_MS)
|
||||
handler.post {
|
||||
setStatus("Waiting for server... (${attempt + 1}/$MAX_PROBE_ATTEMPTS)")
|
||||
}
|
||||
}
|
||||
|
||||
handler.post {
|
||||
if (ready) {
|
||||
setStatus("")
|
||||
webView.loadUrl(url)
|
||||
} else {
|
||||
setStatus("Could not connect. Restart the app.")
|
||||
}
|
||||
if (!ready) {
|
||||
handler.post { setStatus("Could not connect. Restart the app.") }
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ class ThefeedService : Service() {
|
||||
super.onCreate()
|
||||
createNotificationChannel()
|
||||
startForeground(NOTIFICATION_ID, buildNotification("Starting local service..."))
|
||||
savePort(-1) // Clear stale port from any previous (force-killed) session
|
||||
startClientProcessAsync()
|
||||
}
|
||||
|
||||
|
||||
@@ -74,11 +74,13 @@ func (rc *ResolverChecker) CheckNow() {
|
||||
return
|
||||
}
|
||||
|
||||
rc.log("Checking %d resolver(s)...", len(resolvers))
|
||||
total := len(resolvers)
|
||||
rc.log("RESOLVER_SCAN start %d", total)
|
||||
|
||||
var healthy []string
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
var done int
|
||||
wg := &sync.WaitGroup{}
|
||||
sem := make(chan struct{}, 10) // probe up to 10 resolvers concurrently
|
||||
|
||||
for _, r := range resolvers {
|
||||
@@ -88,14 +90,17 @@ func (rc *ResolverChecker) CheckNow() {
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
|
||||
if rc.checkOne(r) {
|
||||
mu.Lock()
|
||||
ok := rc.checkOne(r)
|
||||
mu.Lock()
|
||||
if ok {
|
||||
healthy = append(healthy, r)
|
||||
mu.Unlock()
|
||||
rc.log("Resolver OK: %s", r)
|
||||
} else {
|
||||
rc.log("Resolver failed: %s", r)
|
||||
}
|
||||
done++
|
||||
rc.log("RESOLVER_SCAN progress %d/%d", done, total)
|
||||
mu.Unlock()
|
||||
}(r)
|
||||
}
|
||||
wg.Wait()
|
||||
@@ -103,9 +108,11 @@ func (rc *ResolverChecker) CheckNow() {
|
||||
rc.fetcher.SetActiveResolvers(healthy)
|
||||
if len(healthy) == 0 {
|
||||
rc.log("Resolver check done: 0/%d healthy", len(resolvers))
|
||||
rc.log("RESOLVER_SCAN done 0/%d", total)
|
||||
return
|
||||
}
|
||||
rc.log("Resolver check done: %d/%d healthy", len(healthy), len(resolvers))
|
||||
rc.log("RESOLVER_SCAN done %d/%d", len(healthy), total)
|
||||
}
|
||||
|
||||
// checkOne probes a single resolver by sending a metadata channel query
|
||||
|
||||
@@ -217,6 +217,7 @@ html[dir=ltr] .active-badge{margin-left:0;margin-right:6px}
|
||||
<span class="profile-btn-arrow">▼</span>
|
||||
</button>
|
||||
<button class="icon-btn" onclick="openSettings()" title="Settings" data-i18n-title="settings">⚙</button>
|
||||
<button class="icon-btn" onclick="jumpToLog()" title="LOG">📜</button>
|
||||
</div>
|
||||
<input class="sidebar-search" id="channelSearch" type="text" data-i18n-ph="search" placeholder="Search..." oninput="filterChannels()">
|
||||
</div>
|
||||
@@ -840,6 +841,8 @@ function addLogLine(line){
|
||||
var div=document.createElement('div');
|
||||
var cls='inf';
|
||||
if(typeof line==='string'){
|
||||
// Handle structured resolver scan events — show progress bar, suppress from log
|
||||
if(line.startsWith('RESOLVER_SCAN ')){updateResolverScanDisplay(line);return}
|
||||
if(line.includes('Error:')||line.includes('error')||line.includes('Invalid passphrase'))cls='err';
|
||||
else if(line.includes('Warning:'))cls='warn';
|
||||
else if(line.includes('OK')||line.includes('success')||line.includes('done'))cls='ok';
|
||||
@@ -850,6 +853,43 @@ function addLogLine(line){
|
||||
while(el.children.length>200)el.removeChild(el.firstChild);
|
||||
}
|
||||
|
||||
function updateResolverScanDisplay(line){
|
||||
var panel=document.getElementById('progressPanel');
|
||||
var item=document.getElementById('prog-resolvers');
|
||||
// RESOLVER_SCAN start N
|
||||
var startMatch=line.match(/^RESOLVER_SCAN start (\d+)/);
|
||||
if(startMatch){
|
||||
var total=parseInt(startMatch[1]);
|
||||
if(!item){
|
||||
item=document.createElement('div');item.id='prog-resolvers';item.className='progress-item';
|
||||
item.innerHTML='<button class="progress-close" onclick="this.parentNode.remove()" title="Dismiss">×</button><div class="progress-label"></div><div class="progress-bar"><div class="progress-fill" style="width:0%"></div></div>';
|
||||
panel.insertBefore(item,panel.firstChild);
|
||||
}
|
||||
item.dataset.total=total;
|
||||
item.querySelector('.progress-label').textContent='Scanning resolvers 0/'+total;
|
||||
item.querySelector('.progress-fill').style.width='0%';
|
||||
return;
|
||||
}
|
||||
if(!item)return;
|
||||
// RESOLVER_SCAN progress D/T
|
||||
var progMatch=line.match(/^RESOLVER_SCAN progress (\d+)\/(\d+)/);
|
||||
if(progMatch){
|
||||
var done=parseInt(progMatch[1]),tot=parseInt(progMatch[2]);
|
||||
var pct=Math.round((done/tot)*100);
|
||||
item.querySelector('.progress-label').textContent='Scanning resolvers '+done+'/'+tot;
|
||||
item.querySelector('.progress-fill').style.width=pct+'%';
|
||||
return;
|
||||
}
|
||||
// RESOLVER_SCAN done K/T
|
||||
var doneMatch=line.match(/^RESOLVER_SCAN done (\d+)\/(\d+)/);
|
||||
if(doneMatch){
|
||||
var healthy=parseInt(doneMatch[1]),total2=parseInt(doneMatch[2]);
|
||||
item.querySelector('.progress-label').textContent='Resolvers ready: '+healthy+'/'+total2+' active';
|
||||
item.querySelector('.progress-fill').style.width='100%';
|
||||
setTimeout(function(){if(item.parentNode)item.parentNode.removeChild(item)},2000);
|
||||
}
|
||||
}
|
||||
|
||||
function updateProgressDisplay(line){
|
||||
var match=line.match(/Channel\s+(\d+)/);if(!match)return;
|
||||
var channelNum=parseInt(match[1]);
|
||||
@@ -878,13 +918,24 @@ function toggleLog(){
|
||||
p.classList.toggle('hidden',!logVisible);
|
||||
ic.innerHTML=logVisible?'▼':'▶';
|
||||
}
|
||||
function openLog(){
|
||||
if(logVisible)return;
|
||||
logVisible=true;
|
||||
document.getElementById('logPanel').classList.remove('hidden');
|
||||
document.getElementById('logToggleIcon').innerHTML='▼';
|
||||
}
|
||||
function jumpToLog(){
|
||||
openChat();
|
||||
openLog();
|
||||
setTimeout(function(){document.getElementById('logPanel').scrollIntoView({behavior:'smooth',block:'end'})},300);
|
||||
}
|
||||
|
||||
// ===== REFRESH =====
|
||||
function showInitProgress(){
|
||||
document.getElementById('progressPanel').innerHTML='';
|
||||
var p=document.getElementById('progressPanel');
|
||||
p.innerHTML='<div class="progress-item" id="prog-init"><button class="progress-close" onclick="this.parentNode.remove()" title="Dismiss">×</button><div class="progress-label">'+t('loading')+'</div><div class="progress-bar"><div class="progress-fill" style="width:30%;animation:prog-pulse 1.5s ease-in-out infinite"></div></div></div>';
|
||||
if(window.innerWidth<=768) openChat();
|
||||
if(window.innerWidth<=768){openChat();openLog();}
|
||||
}
|
||||
function startAutoRefresh(){if(autoRefreshTimer)return;autoRefreshTimer=setInterval(function(){if(selectedChannel>0)doRefresh(true)},600000)}
|
||||
function updateNextFetchDisplay(){
|
||||
|
||||
@@ -564,6 +564,7 @@ func (s *Server) startCheckerThenRefresh() {
|
||||
return
|
||||
}
|
||||
checker.StartAndNotify(ctx, func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
s.refreshMetadataOnly()
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user