Discussion

Python ile Dosya Senkronizasyonu

Started by QuantumBerk · 09 Jul 2026 10:56 · 18 Views · 0 Replies
Thread Starter #0
Python ile Dosya Senkronizasyonu

Dosya senkronizasyonu, modern bilişim dünyasında en temel ama aynı zamanda en kritik ihtiyaçlardan biri, değil mi? Geliştirici olarak hepimizin başına gelmiştir; bir projeyi birden fazla ortamda tutmak, yedeklemek, ekip arkadaşlarıyla senkronize çalışmak ya da sadece önemli verilerin kaybolmadığından emin olmak... İşte tam bu noktada, Python gibi esnek ve güçlü bir dilin kapıları bize sonuna kadar açılıyor. Kendi senkronizasyon aracımızı yazmak, hem bize süreç üzerinde tam kontrol sağlıyor hem de genel mantığı derinlemesine anlamamıza yardımcı oluyor.

Peki, neden Python? Bu iş için hazır araçlar varken neden tekerleği yeniden icat edelim ki? Aslında bu, "yeniden icat etmek"ten ziyade, "ihtiyaçlarımıza göre şekillendirmek" ve "nasıl çalıştığını anlamak" meselesi. Python'ın basit sözdizimi, zengin standart kütüphanesi ve platform bağımsız yapısı, dosya işlemleri için onu mükemmel bir aday yapıyor. İster Windows, ister Linux, ister macOS olsun, aynı kod çoğu zaman sorunsuz çalışır.

Dosya Senkronizasyonunun Temelleri: Ne Anlamalıyız?

Bir senkronizasyon aracı yazmaya başlamadan önce, temel prensipleri netleştirmek şart. Birincisi, kaynak (source) ve hedef (destination) dizinlerimiz olacak. Amacımız, kaynak dizindeki değişiklikleri hedefe yansıtmak. Bu yansıtma işlemi, tek yönlü veya çift yönlü olabilir.

  • Tek Yönlü Senkronizasyon (One-Way Sync): Kaynak dizini hedefe kopyalar, hedeften kaynağa herhangi bir değişiklik aktarılmaz. En basit ve en yaygın senkronizasyon türü, genellikle yedekleme senaryolarında kullanılır. Kaynaktaki bir dosya değişirse, hedefteki üzerine yazılır. Kaynakta yeni bir dosya oluşursa, hedefe kopyalanır. Kaynaktan bir dosya silinirse... işte bu kısım biraz tartışmalı. Hedefte de silmeli miyiz, yoksa sadece yeni/değişenleri mi kopyalamalıyız? Bu tamamen bizim tasarım kararımız.
  • Çift Yönlü Senkronizasyon (Two-Way Sync): Her iki dizindeki değişiklikler karşılıklı olarak birbirine yansıtılır. Bu, genellikle işbirliği yapılan ortamlarda veya cihazlar arasında veri bütünlüğünü sağlamak için kullanılır. Ancak, burada devreye çakışma (conflict) kavramı giriyor. Aynı dosya hem kaynakta hem de hedefte farklı şekillerde değişirse ne olacak? Bu tür durumlar için bir çözüm stratejisi belirlemek zorundayız.

Biz bu yazıda, başlangıç için daha anlaşılır olan tek yönlü senkronizasyon üzerine odaklanacağız, ancak çift yönlünün temellerini de konuşuruz.

Değişiklikleri Nasıl Tespit Ederiz?

Bir dosyanın değişip değişmediğini anlamak, senkronizasyonun kalbi. Bunun için birkaç yaygın yöntem var:

  • Dosya Boyutu ve Son Değişiklik Tarihi (Timestamp): En basit yöntem. Dosyanın boyutu veya son değiştirilme tarihi farklıysa, dosyanın değiştiğini varsayarız. Python'da
    CODE
    1os.path.getsize()
    ve
    CODE
    1os.path.getmtime()
    ile bu bilgilere kolayca erişebiliriz. Ancak bu yöntem, bazı senaryolarda yanıltıcı olabilir. Örneğin, bir dosyanın içeriği değişmeden sadece metadata'sı güncellenirse (ki bu nadirdir ama olabilir), timestamp değişir. Veya tam tersi, bir dosya kopyalanırken timestamp'i orijinaliyle aynı tutulabilir, bu da değişiklik olmadığını düşündürebilir.
  • Sağlama Toplamları (Checksums/Hashes): Daha güvenilir bir yöntem. Dosyanın içeriğinden benzersiz bir "parmak izi" (hash) üretiriz. SHA256 veya MD5 gibi algoritmalarla üretilen bu hash değerleri, dosyanın içeriğindeki en ufak bir değişikliği bile anında ortaya çıkarır. İki dosyanın hash'i aynıysa, içeriklerinin de aynı olduğunu varsayabiliriz. Bu, özellikle büyük ve kritik dosyalar için önerilen bir yöntem. Python'ın hashlib modülü bu iş için biçilmiş kaftan.

Biz scriptimizde hem timestamp'i hem de hash'i kullanarak daha sağlam bir karşılaştırma yapalım. Özellikle büyük dosyalar için sadece timestamp'e bakmak başlangıçta daha hızlı olabilir, ama kesinlik gerektiğinde hash'ler devreye girmeli.

Python'ın Standart Kütüphanesi ile Senkronizasyon

Python, dosya ve dizin işlemleri için harika modüllere sahip. İşte en çok kullanacaklarımız:

  • os: İşletim sistemi ile etkileşim kurmak için temel modül. Dizinleri listeleme (
    CODE
    1os.listdir()
    ,
    CODE
    1os.walk()
    ), dosya yolları ile çalışma (
    CODE
    1os.path.join()
    ,
    CODE
    1os.path.exists()
    ), dosya/dizin istatistiklerini alma (
    CODE
    1os.stat()
    ) gibi işlevler sunar.
  • shutil: Daha yüksek seviyeli dosya işlemleri için. Dosyaları kopyalama (
    CODE
    1shutil.copy2()
    ), dizinleri kopyalama (
    CODE
    1shutil.copytree()
    ), silme (
    CODE
    1shutil.rmtree()
    ) gibi operasyonlar için ideal.
    CODE
    1copy2()
    fonksiyonu, dosyanın metadata'sını (son değiştirilme tarihi gibi) da koruduğu için senkronizasyon için çok kullanışlıdır.
  • hashlib: Şifreleme hash'leri oluşturmak için. Dosya içeriklerinin bütünlüğünü kontrol etmek için SHA256 gibi algoritmaları kullanacağız.
  • pathlib (Opsiyonel ama Modern): os.path modülüne modern ve nesne tabanlı bir alternatif. Dosya yollarını daha sezgisel bir şekilde manipüle etmeyi sağlar. Yeni projelerde genellikle tercih edilir.

Şimdi basit bir senkronizasyon mantığı kuralım. Amacımız, kaynak dizindeki dosyaları hedef dizine tek yönlü olarak senkronize etmek. Hedef dizinde kaynakta olmayan bir dosya varsa, onu sileceğiz (mirroring).

Adım Adım Basit Bir Senkronizasyon Scripti

Haydi gelin, senkronizasyon mantığını bir Python scripti üzerinde adım adım inşa edelim.

CODE
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
import os
import shutil
import hashlib
import time # İlerleme çubuğu veya benzeri için kullanılabilir, şimdilik sadece zaman damgası için

def calculate_file_hash(filepath, block_size=65536):
    """Verilen dosyanın SHA256 hash değerini hesaplar."""
    hasher = hashlib.sha256()
    try:
        with open(filepath, 'rb') as f:
            for block in iter(lambda: f.read(block_size), b''):
                hasher.update(block)
        return hasher.hexdigest()
    except FileNotFoundError:
        return None # Dosya bulunamazsa None döner
    except Exception as e:
        print(f"Hata: {filepath} dosyasının hash'i hesaplanırken sorun oluştu: {e}")
        return None

def get_file_metadata(filepath):
    """Dosyanın boyutunu, son değişiklik zamanını ve hash'ini döner."""
    if not os.path.exists(filepath):
        return None
    
    try:
        stat = os.stat(filepath)
        size = stat.st_size
        mtime = stat.st_mtime
        file_hash = calculate_file_hash(filepath) # Her dosya için hash hesaplamak zaman alabilir!
        return {'size': size, 'mtime': mtime, 'hash': file_hash}
    except Exception as e:
        print(f"Hata: {filepath} dosyasının meta verileri alınırken sorun oluştu: {e}")
        return None

def sync_directories(source_dir, dest_dir, dry_run=False):
    """
    Kaynak dizini hedef dizin ile senkronize eder.
    Tek yönlü senkronizasyon (mirroring).
    """
    if not os.path.exists(source_dir):
        print(f"Hata: Kaynak dizin bulunamadı: {source_dir}")
        return
    
    if not os.path.exists(dest_dir):
        print(f"Hedef dizin bulunamadı, oluşturuluyor: {dest_dir}")
        os.makedirs(dest_dir)

    print(f"\nSenkronizasyon başlatılıyor: '{source_dir}' -> '{dest_dir}' {'(Deneme Modu)' if dry_run else ''}")

    # Kaynak dizindeki tüm dosyaları ve dizinleri tara
    source_items = {} # Tam yollarını tutacak
    for root, dirs, files in os.walk(source_dir):
        for name in files:
            source_path = os.path.join(root, name)
            relative_path = os.path.relpath(source_path, source_dir)
            source_items[relative_path] = source_path
        for name in dirs:
            source_path = os.path.join(root, name)
            relative_path = os.path.relpath(source_path, source_dir)
            # Dizinleri de takip edelim, ancak sadece varlıklarını kontrol etmek için
            # İçlerindeki dosyalar zaten 'files' döngüsünde işlenecek.
            # Şimdilik sadece dosyalarla ilgilenelim.

    # Hedef dizindeki tüm dosyaları ve dizinleri tara
    dest_items = {}
    for root, dirs, files in os.walk(dest_dir):
        for name in files:
            dest_path = os.path.join(root, name)
            relative_path = os.path.relpath(dest_path, dest_dir)
            dest_items[relative_path] = dest_path

    # Adım 1: Kaynaktan hedefe kopyala/güncelle
    for relative_path, source_full_path in source_items.items():
        dest_full_path = os.path.join(dest_dir, relative_path)

        if not os.path.exists(dest_full_path):
            # Dosya hedefte yok, kopyala
            print(f"[+] Kopyalanıyor: '{relative_path}'")
            if not dry_run:
                os.makedirs(os.path.dirname(dest_full_path), exist_ok=True)
                shutil.copy2(source_full_path, dest_full_path)
        else:
            # Dosya hem kaynakta hem de hedefte var, karşılaştır
            source_meta = get_file_metadata(source_full_path)
            dest_meta = get_file_metadata(dest_full_path)

            if source_meta is None or dest_meta is None:
                print(f"[!] Meta veri alınamadı, atlanıyor: '{relative_path}'")
                continue

            # Hash'ler farklıysa veya boyut ve mtime farklıysa güncelle
            # Hash kontrolü en güveniliridir.
            if source_meta['hash'] != dest_meta['hash']:
                print(f"[U] Güncelleniyor: '{relative_path}' (Hash farklı)")
                if not dry_run:
                    shutil.copy2(source_full_path, dest_full_path)
            # Eğer hash'ler aynıysa ama mtime farklıysa (çok nadir, ama olabilir)
            # Veya boyutlar farklıysa ama hash aynıysa (imkansız)
            # Sadece hash kontrolü yeterli olmalı. Ama yine de bir kontrol daha.
            elif source_meta['size'] != dest_meta['size'] or source_meta['mtime'] > dest_meta['mtime']:
                print(f"[U] Güncelleniyor: '{relative_path}' (Boyut veya Zaman farklı)")
                if not dry_run:
                    shutil.copy2(source_full_path, dest_full_path)
            else:
                #print(f"[-] Aynı: '{relative_path}'") # Çok fazla çıktı olmaması için yorum satırı yaptım
                pass

    # Adım 2: Hedefte olup kaynakta olmayanları sil (Mirroring özelliği)
    for relative_path, dest_full_path in dest_items.items():
        source_full_path = os.path.join(source_dir, relative_path)
        
        if not os.path.exists(source_full_path):
            # Dosya kaynakta yok, hedefte sil
            if os.path.isfile(dest_full_path):
                print(f"[-] Siliniyor: '{relative_path}'")
                if not dry_run:
                    os.remove(dest_full_path)
            elif os.path.isdir(dest_full_path):
                # Eğer silinecek olan bir dizinse, içini boşaltıp silmeliyiz.
                # shutil.rmtree() kullanabiliriz, ancak dikkatli olunmalı.
                # Burada sadece dosyaları siliyoruz, dizinleri boşaltma ve silme daha karmaşık.
                # Basitlik adına, sadece dosyaları ele alalım.
                pass 
                
    # Boş kalan dizinleri temizlemek için ek bir adım gerekebilir.
    # Bu scriptte şimdilik sadece dosyaları siliyoruz.
    # shutil.rmtree(path) ile tüm dizini silme işlemi, çok dikkatli kullanılmalı.

    print(f"\nSenkronizasyon tamamlandı! {'(Deneme Modu)' if dry_run else ''}")

[b]Örnek Kullanım:[/b]
if [b]name[/b] == "[b]main[/b]":
    # Test için örnek dizinler oluşturalım
    base_test_dir = "sync_test_env"
    source_example_dir = os.path.join(base_test_dir, "kaynak")
    dest_example_dir = os.path.join(base_test_dir, "hedef")

    # Temiz bir başlangıç için eski test ortamını sil
    if os.path.exists(base_test_dir):
        shutil.rmtree(base_test_dir)
        print(f"Eski test ortamı silindi: {base_test_dir}")
    
    os.makedirs(source_example_dir, exist_ok=True)
    os.makedirs(dest_example_dir, exist_ok=True)

    # Kaynak dizine bazı dosyalar ekleyelim
    with open(os.path.join(source_example_dir, "dosya1.txt"), "w") as f:
        f.write("Bu birinci dosya içeriği.")
    with open(os.path.join(source_example_dir, "dosya2.txt"), "w") as f:
        f.write("Bu ikinci dosya içeriği.")
    os.makedirs(os.path.join(source_example_dir, "alt_dizin"), exist_ok=True)
    with open(os.path.join(source_example_dir, "alt_dizin", "dosya3.txt"), "w") as f:
        f.write("Bu alt dizindeki dosya.")

    print("\n--- İlk Senkronizasyon (Deneme Modu) ---")
    sync_directories(source_example_dir, dest_example_dir, dry_run=True)

    print("\n--- İlk Senkronizasyon (Gerçek Mod) ---")
    sync_directories(source_example_dir, dest_example_dir, dry_run=False)

    # Değişiklikler yapalım
    print("\n--- Değişiklikler yapılıyor ---")
    with open(os.path.join(source_example_dir, "dosya1.txt"), "a") as f: # Dosya1'i güncelleyelim
        f.write("\nEklenen yeni satır.")
    
    with open(os.path.join(source_example_dir, "yeni_dosya.txt"), "w") as f: # Yeni bir dosya ekleyelim
        f.write("Bu yeni eklenen dosya.")
    
    os.remove(os.path.join(source_example_dir, "dosya2.txt")) # Dosya2'yi silelim
    
    # Hedefte fazladan bir dosya oluşturalım (kaynakta olmayan)
    with open(os.path.join(dest_example_dir, "fazladan_dosya.txt"), "w") as f:
        f.write("Bu hedefte fazladan olan dosya.")

    print("\n--- İkinci Senkronizasyon (Değişiklikleri Uygulama) ---")
    sync_directories(source_example_dir, dest_example_dir, dry_run=False)

    # Senkronize edilmiş dizinleri kontrol edelim
    print("\n--- Senkronizasyon sonrası dizin içeriği ---")
    print(f"Kaynak ({source_example_dir}): {os.listdir(source_example_dir)}")
    print(f"Hedef ({dest_example_dir}): {os.listdir(dest_example_dir)}")
    print(f"Kaynak alt_dizin: {os.listdir(os.path.join(source_example_dir, 'alt_dizin'))}")
    print(f"Hedef alt_dizin: {os.listdir(os.path.join(dest_example_dir, 'alt_dizin'))}")


Yukarıdaki kodda gördüğünüz gibi,
CODE
1calculate_file_hash
fonksiyonu ile dosyaların SHA256 hash'ini hesaplıyoruz. Bu, dosya içeriğinin gerçekten değişip değişmediğini anlamanın en güvenilir yolu.
CODE
1get_file_metadata
ise hem boyutu, hem son değiştirilme zamanını hem de hash'i tek bir yerde topluyor.

CODE
1sync_directories
fonksiyonumuz ise iki ana adımdan oluşuyor:

  • Kaynaktan Hedefe Kopyalama/Güncelleme:
    CODE
    1os.walk()
    ile kaynak dizini gezerek her bir dosyayı inceliyoruz. Eğer dosya hedefte yoksa kopyalıyoruz. Varsa, meta verilerini (boyut, zaman damgası, hash) karşılaştırıyoruz. Hash'ler farklıysa, dosyanın değiştiğini varsayıp üzerine yazıyoruz.
  • Hedefte Olup Kaynakta Olmayanları Silme: Bu kısım, "mirroring" (yansılama) olarak adlandırılır. Hedef dizini de geziyoruz. Eğer hedefte bir dosya varsa ama kaynakta karşılığı yoksa, bu dosyanın kaynakta silindiğini ve hedeften de silinmesi gerektiğini anlıyoruz.

Dikkat Edilmesi Gereken Noktalar ve Geliştirmeler

Bu basit script, temel bir senkronizasyon mantığı sunuyor. Ancak gerçek dünya senaryoları için bazı önemli geliştirmelere ihtiyaç duyar:

  • Performans: Özellikle çok sayıda veya çok büyük dosya ile çalışırken, her dosya için hash hesaplamak zaman alıcı olabilir. Daha akıllı bir yaklaşım, ilk olarak sadece timestamp ve boyutu kontrol etmek, eğer bunlar farklıysa hash hesaplamaya geçmek olabilir. Veya, daha gelişmiş bir çözüm, bir veritabanında (SQLite gibi) her dosyanın hash'ini ve metadata'sını tutarak sadece değişen dosyaların hash'ini yeniden hesaplamak olabilir.
  • Hata Yönetimi: Dosya izinleri, disk alanı eksikliği, dosya kilitlenmeleri gibi birçok hata durumu ortaya çıkabilir.
    CODE
    1try-except
    blokları ile bu durumları ele almak ve kullanıcıya anlamlı geri bildirimler sunmak önemlidir.
  • Loglama: Hangi dosyaların kopyalandığı, güncellendiği veya silindiği gibi bilgileri bir log dosyasına yazmak, sorun giderme ve takip için paha biçilmezdir. Python'ın logging modülü bu iş için idealdir.
  • Dizin Silme: Şu anki script sadece dosyaları siliyor. Eğer kaynakta bir dizin tamamen silinirse, hedefteki karşılığının da silinmesi gerekir.
    CODE
    1shutil.rmtree()
    fonksiyonu bu işi yapar ancak çok dikkatli kullanılmalıdır, yanlışlıkla önemli verileri silebilirsiniz.
  • İlerleme Göstergesi: Büyük senkronizasyonlarda kullanıcıya işlemin ne kadar ilerlediğini göstermek (örneğin, tqdm kütüphanesi ile) kullanıcı deneyimini artırır.
  • Filtreleme (Exclusion/Inclusion): Belirli dosya türlerini (
    CODE
    1[i].log
    ,
    CODE
    1[/i].tmp
    ) veya dizinleri (
    CODE
    1.git/
    ,
    CODE
    1node_modules/
    ) senkronizasyon dışında bırakmak isteyebiliriz. Bu, bir
    CODE
    1.gitignore
    benzeri dosya veya komut satırı argümanları ile yapılandırılabilir.
  • Çift Yönlü Senkronizasyon ve Çakışma Çözümü: Bu, işin en karmaşık kısmı. Eğer hem kaynakta hem de hedefte aynı dosya değişirse, hangisinin doğru olduğuna karar vermek gerekir. Stratejiler şunlar olabilir:
    • En son değiştirilen sürümü koru.
    • Kullanıcıya sor.
    • Her iki sürümü de farklı isimlerle (örn:
      CODE
      1dosya.txt.kaynak
      ,
      CODE
      1dosya.txt.hedef
      ) sakla.
    Bu, genellikle bir veritabanı tutmayı ve her dosyanın her iki taraftaki durumunu izlemeyi gerektirir.
  • Zamanlama (Scheduling): Scripti belirli aralıklarla çalıştırmak için işletim sisteminin zamanlayıcılarını (cron job, Windows Task Scheduler) kullanabilirsiniz.
  • Gerçek Zamanlı Senkronizasyon: Dosya sistemi olaylarını dinleyerek (dosya oluşturuldu, silindi, değiştirildi) anında senkronizasyon yapmak için watchdog gibi kütüphaneler kullanılabilir. Bu, daha karmaşık bir yapı gerektirir ancak anında yansıma sağlar.
  • Uzak Senkronizasyon: Şu anki script yerel dizinler arasında çalışıyor. FTP, SFTP, S3 gibi uzak depolama alanlarıyla senkronizasyon yapmak için ftplib, paramiko (SSH/SFTP için) veya AWS boto3 gibi kütüphaneleri entegre etmek gerekir.

Gördüğünüz gibi, dosya senkronizasyonu konusu oldukça derinleşebiliyor. Basit bir yedekleme scriptinden, tam teşekküllü bir dağıtık dosya sistemine kadar geniş bir yelpazesi var. Python'ın esnekliği sayesinde, ihtiyacımız olan özelliklere odaklanarak kendi çözümümüzü adım adım inşa edebiliriz.

Sizlerin bu konuda deneyimleri nelerdir? Daha önce Python ile benzer bir senkronizasyon aracı geliştirdiniz mi? Hangi zorluklarla karşılaştınız veya hangi kütüphaneleri kullandınız? Yorumlarda fikirlerinizi ve tecrübelerinizi paylaşmaktan çekinmeyin, hep birlikte öğrenelim...

You must be logged in to reply.

0 quotes selected