惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Blog — PlanetScale
Blog — PlanetScale
Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
量子位
Y
Y Combinator Blog
IT之家
IT之家
博客园 - 聂微东
L
LangChain Blog
爱范儿
爱范儿
H
Help Net Security
GbyAI
GbyAI
F
Fortinet All Blogs
B
Blog
Microsoft Security Blog
Microsoft Security Blog
罗磊的独立博客
C
Check Point Blog
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
D
DataBreaches.Net
Last Week in AI
Last Week in AI
WordPress大学
WordPress大学
B
Blog RSS Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
宝玉的分享
宝玉的分享

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Conflict Resolution in a Bidirectional Sync App — How I H...
hiyoyo · 2026-06-19 · via DEV Community
Cover image for Conflict Resolution in a Bidirectional Sync App — How I Handle the Hard Cases

hiyoyo

All tests run on an 8-year-old MacBook Air. All results from shipping 7 Mac apps as a solo developer. No sponsored opinion.

HiyokoAutoSync does bidirectional sync between Android and Mac. Bidirectional sync has a hard problem: what happens when the same file is modified on both sides? Here's how I handle it.


The conflict cases

  1. File modified on both sides since last sync — which version wins?
  2. File deleted on one side, modified on the other — delete or keep?
  3. File moved on one side — move on the other side or treat as delete + create?

Most sync apps punt on cases 1 and 3. Here's my approach.


Detecting conflicts

Track last-synced state in SQLite:

CREATE TABLE sync_state (
    file_path TEXT PRIMARY KEY,
    mac_hash TEXT,
    android_hash TEXT,
    mac_modified INTEGER,
    android_modified INTEGER,
    last_synced INTEGER
);

On sync check:

fn classify_file(record: &SyncRecord, mac_stat: &FileStat, android_stat: &FileStat) -> SyncAction {
    let mac_changed = mac_stat.hash != record.mac_hash;
    let android_changed = android_stat.hash != record.android_hash;

    match (mac_changed, android_changed) {
        (true, false)  => SyncAction::CopyToAndroid,
        (false, true)  => SyncAction::CopyToMac,
        (false, false) => SyncAction::NoOp,
        (true, true)   => SyncAction::Conflict,
    }
}


Conflict resolution strategies

I offer three strategies, user-configurable:

Newer wins: compare modification timestamps, keep the more recent file.

SyncAction::Conflict => {
    if mac_stat.modified > android_stat.modified {
        SyncAction::CopyToAndroid
    } else {
        SyncAction::CopyToMac
    }
}

Mac always wins: for users who treat Mac as source of truth.

Keep both: rename one file with a conflict suffix, keep both versions.

// Rename Android version to "file.conflict-2026-05-01.ext"
let conflict_name = add_conflict_suffix(&file_path);
copy_to_mac_as(&android_file, &conflict_name)?;
copy_to_android(&mac_file)?;


Delete vs modify conflict

File deleted on Mac, modified on Android:

(Deleted, Modified) => {
    // Default: keep the modified file, restore it on Mac
    // Alternative: delete from both sides
    // User configurable
    SyncAction::RestoreToMac
}

The safe default is to keep data. Deleting across both sides on a conflict can cause data loss users didn't intend.


The verdict

Bidirectional sync without conflict resolution is a bug waiting to happen. The "newer wins" strategy covers 90% of real-world cases. Keep both covers the rest. Make it configurable for power users.


TL;DR: Track sync state (hash + modified time) in SQLite per file. Classify each file into CopyToAndroid, CopyToMac, NoOp, or Conflict using a match on what changed. Offer three strategies: newer wins, Mac wins, or keep both. For delete vs modify conflicts, default to keeping data — accidental deletion is worse than a duplicate.


If this was useful, a ❤️ helps more than you'd think — thanks!

HiyokoAutoSync | X → @hiyoyok