#include "istakip_servis.hpp" #include #include #include #include #include "fiko/cekirdek/gecici.hpp" // indirilen belgeler gecici/istakip/dosyalar altına #include "fiko/cekirdek/olay.hpp" #include "istakip_sema.hpp" // F3: alan adı → etiket çevirisi (turlarOku) #include "orkestra_kule.hpp" // orkestraNodeId (kalıcı makine kimliği — aynı kimlik) #if FIKO_ISTAKIP_MONGO #include "fiko/istakip/depo_mongo.hpp" #endif #include // GetComputerNameA (nodeId türetimi kule ile aynı kaynak) namespace fiko { namespace { uint64_t yerelIstakipNo() { char ad[MAX_COMPUTERNAME_LENGTH + 1] = {}; DWORD n = sizeof(ad); GetComputerNameA(ad, &n); return orkestraNodeId(ad); } // Mongo URI'sine kısa sunucu-seçim zaman aşımı ekle (kullanıcı vermediyse): // kral kurulumu ana iplikte tek sefer denenir — 30 sn varsayılan kilit olmasın. std::string uriZamanAsimiEkle(const std::string& uri) { if (uri.find("serverSelectionTimeoutMS") != std::string::npos) return uri; return uri + (uri.find('?') == std::string::npos ? "?" : "&") + "serverSelectionTimeoutMS=2000"; } // F3: alan adını şemadaki etikete çevirir (bulunamazsa alan adının kendisi). std::string alanEtiketi(const std::vector& semalar, const std::string& tur, const std::string& ad) { for (const TurSema& t : semalar) if (t.tur_id == tur) for (const AlanSema& a : t.alanlar) if (a.ad == ad) return a.etiket; return ad; } } // namespace IstakipServis g_istakipServis; bool IstakipServis::senkronMu() const { if (merkez_) return true; return istemciKanal_ && istemciKanal_->bagli(); } std::string IstakipServis::durumMetni() const { if (merkez_) { return "Kral · " + std::to_string(kralTasima_ ? kralTasima_->baglantiSayisi() : 0) + " istemci · depo:" + (depo_ ? depo_->ad() : "-"); } if (istemciTasima_) { return senkronMu() ? ("Bağlı · " + istemciAdres_) : ("Bağlanıyor… · " + istemciAdres_); } return "Kapalı"; } void IstakipServis::kralKur(OlayGunlugu& olaylar) { const std::string uri = istakipMongoUri(); #if FIKO_ISTAKIP_MONGO auto mongo = std::make_unique(uriZamanAsimiEkle(uri)); if (!mongo->acik()) { sonHata_ = "Mongo bağlantısı başarısız: " + mongo->hata(); olaylar.yayinla(IsKralKoptu{}, "Kral kurulamadı: Mongo yok", OlaySiddet::Hata); return; // tekrar deneme YOK (bayrak kapat-aç ile sıfırlanır) — ana iplik kilitlenmesin } depo_ = std::move(mongo); #else // Mongo'suz derleme (geliştirici): bellek deposu — KALICI DEĞİL, uyarı bas. depo_ = std::make_unique(); olaylar.yayinla(IsKralBaglandi{}, "UYARI: bellek deposu (Mongo derlenmedi)", OlaySiddet::Uyari); #endif merkez_ = std::make_unique(*depo_); kralTasima_ = std::make_unique(); if (!kralTasima_->ac(istakipPort(), (size_t)orkestraTcpMaksGovdeMB() * 1024 * 1024, 64)) { sonHata_ = "İş Takip portu açılamadı: " + std::to_string(istakipPort()); olaylar.yayinla(IsKralKoptu{}, sonHata_.c_str(), OlaySiddet::Hata); kralTasima_.reset(); merkez_.reset(); depo_.reset(); return; } kralKanal_ = std::make_unique(*merkez_, *kralTasima_, (size_t)istakipSnapshotParca()); kralTasima_->kopmaAyarla([this](const orkestra::NodeAdres& a) { if (kralKanal_) kralKanal_->istemciDustu(a); }); // Kimlik doğrulama: Orchestrator eşleştirmesinin TOFU anahtarları yeniden // kullanılır (aynı nodeId uzayı) — eşleşmemiş makine zorunlu kipte giremez. kralKanal_->kimlikAyarla( yerelIstakipNo(), [](uint64_t no) -> const std::array* { const orkestra::EslesmeKaydi* k = g_orkestraKule.eslesme().kayitBul(no); return k ? &k->anahtar : nullptr; }, [](uint8_t* p, size_t n) { orkestraRastgeleDoldur(p, n); }, istakipKimlikDogrulama()); // Dosya servisi: yalnız İŞ KAYITLARINDA "dosya" alanı olarak geçen yollar verilir // (path-traversal kapalı); okuma app'te (motor disk bilmez). kralKanal_->dosyaServisAyarla( [this](const std::string& yol) { if (!onbellek_) return false; for (const auto* k : onbellek_->turListe("is_emri")) { const char* d = istakip::alanBul(*k, "dosya"); if (d && yol == d) return true; } return false; }, [](const std::string& yol, std::string& icerik) { std::ifstream f(std::filesystem::path((const char8_t*)yol.c_str()), std::ios::binary); if (!f) return false; icerik.assign(std::istreambuf_iterator(f), std::istreambuf_iterator()); return true; }, (size_t)orkestraTransferParcaKB() * 1024); // Rol kapısı: roller "istemci_rol" İŞ KAYDI olarak tutulur (ayrı depo yok, senkron // bedava): alan no=, durum=izleyici|operatör. Kayıt yoksa genel bayrak. // istemci_rol kayıtlarını UZAKTAN yazmak HER ZAMAN yasak (yalnız kral makinesi atar). kralKanal_->yazIzinAyarla( [this](const orkestra::NodeAdres&, uint64_t no, const istakip::IsKayit& k) { if (k.tur == "istemci_rol") return false; if (onbellek_) { const std::string noStr = std::to_string(no); for (const auto* r : onbellek_->turListe("istemci_rol")) { const char* rn = istakip::alanBul(*r, "no"); if (rn && noStr == rn) { const char* rol = istakip::alanBul(*r, "durum"); return rol && std::string(rol) == "operatör"; } } } return istakipIstemciYazabilir(); }); // Kralın kendi okuma aynası + yazma sırası depodan devam eder. const uint64_t no = yerelIstakipNo(); onbellek_ = std::make_unique(no); onbellek_->snapshotUygula(merkez_->surumdenBeri(0), merkez_->sonSurum(), 0); kralYerelSira_ = merkez_->islenenSira(no); sonYayinSurum_ = merkez_->sonSurum(); olaylar.yayinla(IsKralBaglandi{}, "İş Takip kralı aktif", OlaySiddet::Bilgi); } void IstakipServis::istemciKur(OlayGunlugu& olaylar, const std::string& adres) { std::string ip; uint16_t port = 0; if (!istakipAdresCoz(adres, ip, port, istakipPort())) { sonHata_ = "Geçersiz kral adresi: " + adres; olaylar.yayinla(IsKralKoptu{}, sonHata_.c_str(), OlaySiddet::Uyari); return; } onbellek_ = std::make_unique(yerelIstakipNo()); istemciTasima_ = std::make_unique(); istemciKanal_ = std::make_unique(*onbellek_, *istemciTasima_, orkestra::NodeAdres{0, 0}); istemciKanal_->yazHataAyarla([this](uint64_t sira, const std::string& sebep) { if (sebep == "eslesme") sonHata_ = "Kral kimlik doğrulama istiyor — önce Orchestrator'dan eşleştirin"; else if (sebep == "kimlik") sonHata_ = "Kimlik kanıtı reddedildi — eşleştirmeyi yenileyin"; else sonHata_ = "Yazma reddi (" + sebep + ") sira=" + std::to_string(sira); if (olaylar_) { olaylar_->yayinla(IsKayitDegisti{0}, sonHata_.c_str(), sebep == "depo" ? OlaySiddet::Uyari : OlaySiddet::Hata); } }); // Kimlik sınamasına otomatik kanıt (kralın nodeId'siyle eşleşme kaydı aranır). istemciKanal_->kimlikAyarla([](uint64_t kralNo) -> const std::array* { const orkestra::EslesmeKaydi* k = g_orkestraKule.eslesme().kayitBul(kralNo); return k ? &k->anahtar : nullptr; }); istemciKanal_->dosyaSonucAyarla([this](bool ok, const std::string& yol, const std::string& icerik) { dosyaBekleniyor_ = false; if (!ok) { sonHata_ = "Belge kraldan alınamadı: " + yol; if (olaylar_) olaylar_->yayinla(IsKayitDegisti{0}, sonHata_.c_str(), OlaySiddet::Uyari); return; } // gecici/istakip/dosyalar/ — aynı ad çakışırsa üzerine yazılır (kral kopyası // doğruluk kaynağı; yerel kopya salt görüntüleme). const auto dizin = gecici::altDizin("istakip/dosyalar"); std::string ad = yol; const size_t kes = ad.find_last_of("/\\"); if (kes != std::string::npos) ad = ad.substr(kes + 1); const auto hedef = dizin / std::filesystem::path((const char8_t*)ad.c_str()); std::ofstream f(hedef, std::ios::binary | std::ios::trunc); if (!f || !f.write(icerik.data(), (std::streamsize)icerik.size())) { sonHata_ = "İndirilen belge diske yazılamadı"; if (olaylar_) olaylar_->yayinla(IsKayitDegisti{0}, sonHata_.c_str(), OlaySiddet::Hata); return; } indirilen_[yol] = (const char*)hedef.u8string().c_str(); if (olaylar_) olaylar_->yayinla(IsKayitDegisti{0}, ("Belge indirildi: " + ad).c_str(), OlaySiddet::Bilgi); }); istemciTasima_->baglandiAyarla([this] { if (istemciKanal_) istemciKanal_->baglan(); }); istemciTasima_->koptuAyarla([this] { if (istemciKanal_) istemciKanal_->koptu(); }); istemciTasima_->hedefAyarla(ip, port, (size_t)orkestraTcpMaksGovdeMB() * 1024 * 1024); istemciAdres_ = adres; bagliIdi_ = false; } void IstakipServis::olaylariYayinla(OlayGunlugu& olaylar) { if (!onbellek_) return; // Bağlantı kenarı (istemci): koptu/bağlandı olayları. if (istemciKanal_) { const bool bagli = istemciKanal_->bagli(); if (bagli && !bagliIdi_) olaylar.yayinla(IsKralBaglandi{}, istemciAdres_.c_str(), OlaySiddet::Bilgi); if (!bagli && bagliIdi_) olaylar.yayinla(IsKralKoptu{}, istemciAdres_.c_str(), OlaySiddet::Uyari); bagliIdi_ = bagli; } // Kral aynası: uzak yazmalar merkeze düşer — önbelleğe yansıt. if (merkez_ && merkez_->sonSurum() > onbellek_->sonSurum()) { for (const auto& k : merkez_->surumdenBeri(onbellek_->sonSurum())) onbellek_->deltaUygula(k, 0, 0); } // Veri değişimi → TEK IsKayitDegisti (pencere listeyi tazeler; kayıt-başına // olay seli yok — snapshot'ta binlerce kayıt gelebilir). if (onbellek_->sonSurum() != sonYayinSurum_) { sonYayinSurum_ = onbellek_->sonSurum(); olaylar.yayinla(IsKayitDegisti{0}, "senkron", OlaySiddet::Iz); } } void IstakipServis::tik(OlayGunlugu& olaylar, uint64_t simdiMs) { olaylar_ = &olaylar; const bool kralIste = istakipKral(); const std::string adres = istakipKralAdres(); const bool istemciIste = !kralIste && !adres.empty(); // Yaşam döngüsü geçişleri (bayrak/ayar değişimi). if (!kralIste && merkez_) { kralKanal_.reset(); kralTasima_.reset(); merkez_.reset(); depo_.reset(); onbellek_.reset(); kralDenendi_ = false; } if (!istemciIste && istemciTasima_) { istemciKanal_.reset(); istemciTasima_.reset(); onbellek_.reset(); istemciDenendi_ = false; istemciAdres_.clear(); } if (istemciTasima_ && adres != istemciAdres_) { // hedef değişti → yeniden kur istemciKanal_.reset(); istemciTasima_.reset(); onbellek_.reset(); istemciDenendi_ = false; } if (kralIste && !merkez_ && !kralDenendi_) { kralDenendi_ = true; kralKur(olaylar); } if (istemciIste && !istemciTasima_ && !istemciDenendi_) { istemciDenendi_ = true; istemciKur(olaylar, adres); } if (!kralIste) kralDenendi_ = false; if (!istemciIste) istemciDenendi_ = false; // Nabızlar (yalnız kuruluysa — kapalıyken sıfır iş). if (kralTasima_) kralTasima_->tik(); if (istemciTasima_) istemciTasima_->tik(simdiMs); olaylariYayinla(olaylar); } uint64_t IstakipServis::yaz(const istakip::IsKayit& taslak, uint64_t beklenenSurum) { if (merkez_) { const uint64_t no = onbellek_ ? onbellek_->istemciNo() : 1; // F3: diff için yazmadan ÖNCEKİ hâli kopyala (onbellek_ deltaUygula ile // yerinde güncellenir — sonradan bakınca eski değer kaybolur). std::optional eskiKopya; if (onbellek_ && taslak.kimlik) if (const istakip::IsKayit* e = onbellek_->bul(taslak.kimlik)) eskiKopya = *e; const auto sonuc = merkez_->yaz(no, ++kralYerelSira_, taslak, beklenenSurum); if (!sonuc.uygulandi) { sonHata_ = sonuc.cakisma ? "Çakışma: kayıt başkası tarafından değişti" : (sonuc.depoHata ? "Depo hatası" : "Yazılamadı"); if (olaylar_) olaylar_->yayinla(IsKayitDegisti{taslak.kimlik}, sonHata_.c_str(), OlaySiddet::Hata); return 0; } if (onbellek_) onbellek_->deltaUygula(sonuc.kayit, no, kralYerelSira_); if (kralKanal_) kralKanal_->deltaYayinla(sonuc.kayit, no, kralYerelSira_); olayUret(eskiKopya ? &*eskiKopya : nullptr, sonuc.kayit); return kralYerelSira_; } if (istemciKanal_) return istemciKanal_->yaz(taslak, beklenenSurum); return 0; } void IstakipServis::olayUret(const istakip::IsKayit* eski, const istakip::IsKayit& yeni) { if (!onbellek_ || yeni.tur == "_olay" || yeni.tur == "_sema") return; const uint64_t simdi = (uint64_t)std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); const uint64_t no = onbellek_->istemciNo(); std::vector uretilecek; auto ekle = [&](const std::string& baslik) { istakip::IsKayit o; o.tur = "_olay"; istakip::alanAta(o, istakip::ALAN_UST, std::to_string(yeni.kimlik)); istakip::alanAta(o, "olay_tur", "sistem"); istakip::alanAta(o, "baslik", baslik); istakip::alanAta(o, "yapan", std::to_string(no)); istakip::alanAta(o, "zaman", std::to_string(simdi)); uretilecek.push_back(std::move(o)); }; if (!eski) { ekle("kayıt oluşturuldu"); } else if (yeni.silindi && !eski->silindi) { ekle("kayıt silindi"); } else if (!yeni.silindi) { const auto& semalar = turlarOku(*onbellek_); for (const auto& [ad, deger] : yeni.alanlar) { const char* eskiDeger = istakip::alanBul(*eski, ad); const std::string eskiStr = eskiDeger ? eskiDeger : ""; if (eskiStr == deger) continue; if (ad == "ekler") { // F6: ham JSON yerine okunur mesaj ekle("📎 ek listesi güncellendi"); continue; } const std::string etiket = alanEtiketi(semalar, yeni.tur, ad); if (eskiStr.empty()) ekle(etiket + ": \"" + deger + "\" olarak ayarlandı"); else ekle(etiket + ": " + eskiStr + " → " + deger); } } // "_olay" kendisi guard'lı (yeni.tur=="_olay" en başta elenir) → döngü yok. for (const istakip::IsKayit& o : uretilecek) yaz(o); } bool IstakipServis::dosyaGetir(const std::string& yol) { if (!istemciKanal_ || !istemciKanal_->bagli()) return false; if (!istemciKanal_->dosyaIste(yol)) return false; dosyaBekleniyor_ = true; return true; } std::string IstakipServis::yerelYol(const std::string& yol) const { std::error_code ec; if (std::filesystem::exists(std::filesystem::path((const char8_t*)yol.c_str()), ec)) return yol; // yerel/paylaşım diskinde zaten var const auto it = indirilen_.find(yol); if (it != indirilen_.end() && std::filesystem::exists(std::filesystem::path((const char8_t*)it->second.c_str()), ec)) return it->second; return {}; } void IstakipServis::kapat() { kralKanal_.reset(); kralTasima_.reset(); merkez_.reset(); depo_.reset(); istemciKanal_.reset(); istemciTasima_.reset(); onbellek_.reset(); olaylar_ = nullptr; } } // namespace fiko