using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Driver; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; namespace updateServer { internal class UpdateManager { public static UpdateManager mInstance = null; public static UpdateManager getInstance() { if (mInstance == null) { mInstance = new UpdateManager(); } return mInstance; } private UpdateManager() { init(); ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; } MongoClient mDBClient = null; IMongoDatabase mEventDataBase = null; string mReceivedEventToken = ""; bool isUpdateWork = false; public string GameKey = ""; public string GameName = ""; public bool init() { try { mDBClient = new MongoClient(DEFINE.몽고DB_접속정보); mEventDataBase = mDBClient.GetDatabase("datalol"); //mUpdateWorkerTable = new Dictionary>(); mUpdateWorkerTable = new Dictionary(); return true; } catch (Exception ex) { #if (DEBUG) { System.Windows.Forms.MessageBox.Show(ex.ToString()); } #endif return false; } } //internal Dictionary> mUpdateWorkerTable = null; internal object mWorkerTableLocker = new object(); internal Dictionary mUpdateWorkerTable = null; internal void startUpdateEventRaw(string gameID, bool isProgress) { lock (mWorkerTableLocker) { //bool isRemoved = false; List keys = new List(); foreach (string s in mUpdateWorkerTable.Keys) keys.Add(s); foreach (string s in keys) { mUpdateWorkerTable[s].stopUpdateWork(); mUpdateWorkerTable.Remove(s); //isRemoved = true; } //if (isRemoved) Program.mMainForm.Msg(); if (mUpdateWorkerTable.ContainsKey(gameID)) { mUpdateWorkerTable[gameID].stopUpdateWork(); mUpdateWorkerTable.Remove(gameID); } mUpdateWorkerTable.Add(gameID, new updateWorkObject(gameID, isProgress)); mUpdateWorkerTable[gameID].startUpdateWork(); } } internal void RemoveAllEventRaw() { lock (mWorkerTableLocker) { //bool isRemoved = false; List keys = new List(); foreach (string s in mUpdateWorkerTable.Keys) keys.Add(s); foreach (string s in keys) { mUpdateWorkerTable[s].stopUpdateWork(); mUpdateWorkerTable.Remove(s); } } } public async Task RemoveBackup() { MongoClient dd = new MongoClient(DEFINE.몽고DB_접속정보); var collectionNames = dd.GetDatabase("datalol").ListCollectionNames().ToList(); var mEventDataBaseTarget = dd.GetDatabase("datalol"); foreach (string item in collectionNames) { await mEventDataBaseTarget.GetCollection(item) .DeleteManyAsync(x => true); } mUpdateWorkerTable.Clear(); } /// /// 라이엇 API에 경기중인 게임리스트를 요청한다. /// /// /// internal Dictionary GameListUpdateWorker(bool isTest) { //string bufRequestURL = DEFINE.라이엇_게임리스트_REQUEST_URL + (isTest ? "platformGames" : "esportsGames") + "?state=in_progress";; string bufRequestURL = DEFINE.라이엇_게임리스트_REQUEST_URL + (isTest ? "platformGames" : "esportsGames") + "?state=in_progress"; ; //string bufRequestURL = DEFINE.라이엇_게임리스트_REQUEST_URL + "esportsGames" + "?state=finished"; ; string recvValue = requestRiotData(bufRequestURL); IEnumerable bufGameList = null; List updateRoomList = new List(); Dictionary rtnValue = new Dictionary(); ///가져온 데이터가 null이 아니라면 데이터를 Game단위(Document)로 짤라서 Enumarable화 하고, 아니면 빈 결과를 리턴한다. if (recvValue != null) { bufGameList = BsonSerializer.Deserialize(recvValue).Select(p => p.AsBsonDocument); } else { Program.mMainForm.updateGameRoomList(updateRoomList); return rtnValue; } //가져온 데이터에서 방제와 플랫폼게임ID를 파싱해서 KV페어로 만든다. foreach (BsonValue item in bufGameList) { string bufString = ""; ///20210615테스트할부분 방을 새로만든부분이 있을경우 Document 순서에서 나중에 들어오므로 과거에 들어왔던 데이터를 버린다. ///혹시 모르니 안버린다. //if (rtnValue.ContainsValue(item["gameName"].ToString())) //{ // rtnValue.Remove(rtnValue.FirstOrDefault(x => x.Value == item["gameName"].ToString()).Key); //} if (isTest) { rtnValue.Add(item["platformGameId"].ToString(), item["gameName"].ToString()); bufString = item["platformGameId"].ToString() + "_" + item["gameName"].ToString(); updateRoomList.Add(bufString); } else { BsonDocument itemDocument = item.AsBsonDocument["platformGames"].AsBsonArray.Last().ToBsonDocument(); rtnValue.Add(itemDocument["platformGameId"].ToString(), itemDocument["gameName"].ToString()); bufString = itemDocument["platformGameId"].ToString() + "_" + itemDocument["gameName"].ToString(); updateRoomList.Add(bufString); } #if (DEBUG) { Console.WriteLine(bufString); } #endif } ///완성된 방 정보를 UI에 업데이트한다. Program.mMainForm.updateGameRoomList(updateRoomList); return rtnValue; } /// /// 끝난경기의 정보는 방제만으로 알 수 없기때문에 선수를 검색해서 경기를 찾기위한 게임 구조 클래스 /// internal class game { internal string gamename = ""; internal List playerList = new List(); } /// /// 라이엇 API에서 끝난경기의 정보를 받아와서 gamelist.txt파일로 저장한다. /// /// internal void finishGameListUpdateWorker() { try { Dictionary rtnValue = new Dictionary(); string bufRequestURL = DEFINE.라이엇_게임리스트_REQUEST_URL + "esportsGames" + "?state=finished"; ; string recvValue = requestRiotData(bufRequestURL); IEnumerable bufGameList = null; if (recvValue != null) { bufGameList = BsonSerializer.Deserialize(recvValue).Select(p => p["platformGames"]); } else { return; } foreach (BsonArray item in bufGameList) { BsonDocument selectItem = item.Last().ToBsonDocument(); game bufGame = new game(); bufGame.gamename = selectItem["gameName"].ToString(); BsonArray players = selectItem["participants"].AsBsonArray; List bufplayerList = new List(); foreach (BsonDocument itemp in players) { bufplayerList.Add(itemp["summonerName"].ToString()); } if (players.Count == 0) { string dd = ""; } bufGame.playerList = bufplayerList; rtnValue.Add(selectItem["platformGameId"].ToString(), bufGame); string players123 = ""; foreach (string item2 in bufGame.playerList) { players123 += item2 + "_"; } File.AppendAllText("gameList.txt", selectItem["platformGameId"].ToString() + " " + selectItem["gameName"].ToString() + " " + players123 + Environment.NewLine); } } catch (Exception ex) { System.Windows.Forms.MessageBox.Show("오류발생 - " + ex.Message); } } /// /// 완성된 URL을 통해 라이엇에 데이터를 요청하여 BsonDocument Type으로 결과를 리턴받는다. /// /// /// string requestRiotData(string requestURL) { try { ///20210809 SSL접속문제로 인해 서버인증기능을 끈 부분을 추가한다 이거 이래도 되나? //ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; ///가져온 URL에 맞춰 리퀘스트를 만든다. HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requestURL); WebHeaderCollection hd = new WebHeaderCollection(); hd.Add(DEFINE.RIOT_API_KEY); req.Headers = hd; WebResponse rsp = req.GetResponse(); string result = ""; ///가져온 데이터를 문자열화 하고, Bson형태로 파싱해서 리턴한다. using (var reader = new StreamReader(rsp.GetResponseStream())) { result = reader.ReadToEnd(); } if (result.Trim() == "[]") { return null; } return result; } catch (Exception ex) { return ex.ToString(); } } /// /// EventRaw를 데이터를 업데이트 하기위한 UpdateWorker Class /// internal class updateWorkObject { string mGameID = ""; bool mIsProgress = false; object tokenLocker = new object(); string receviedEventToken = ""; internal string mReceivedEventToken { get { lock (tokenLocker) { return receviedEventToken; } } set { lock (tokenLocker) { receviedEventToken = value; } } } Thread mUpdateWorker = null; internal bool isUpdateWork = false; internal updateWorkObject(string gameID, bool recvProgress) { this.mGameID = gameID; this.mIsProgress = recvProgress; } internal void startUpdateWork() { //mUpdateWorker = new Thread(updateTestDataWork); Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.removeWorkerList(); })); mUpdateWorker = new Thread(updateWork); //mUpdateWorker = new Thread(updateWorkTest); mUpdateWorker.IsBackground = true; isUpdateWork = true; mUpdateWorker.Start(); Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.updateWorkerList(); })); //Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.Msg(); })); } int tokenCount = 0; internal void stopUpdateWork() { isUpdateWork = false; } void updateWork() { while (isUpdateWork) { try { string bufRequestURL = DEFINE.라이엇_이벤트행데이터_REQUEST_URL + mGameID + "/events?paginationToken=" + mReceivedEventToken; BsonDocument bufRecvPayload = BsonDocument.Parse(UpdateManager.getInstance().requestRiotData(bufRequestURL)); ///경기가 끝났음 if (bufRecvPayload.Contains("missingEventsStatus")) { if (bufRecvPayload["missingEventsStatus"].ToString() == "lost_permanently") { isUpdateWork = false; } else { mReceivedEventToken = bufRecvPayload["nextPageToken"].ToString(); } } else if (bufRecvPayload.Contains("nextPageToken")) { mReceivedEventToken = bufRecvPayload["nextPageToken"].ToString(); } //다음페이지토큰이 들어오지 않는 상황에는 updatework를 종료하도록 유도함. else if (!bufRecvPayload.Contains("nextPageToken")) { isUpdateWork = false; } ///20210516 이 방식대로 데이터를 쌓게 되면 데이터를 파싱하는데 너무 복잡하고 오래걸림 //UpdateManager.getInstance().mEventDataBase.GetCollection((mIsProgress ? "in_progress_" : "") + "event_raw") // .UpdateOne( // x => x["nextPageToken"] == bufRecvPayload["nextPageToken"], // Builders.Update.Set(x => x["events"], bufRecvPayload["events"]), // new UpdateOptions() { IsUpsert = true } // ); ///토큰에 할당된 이벤트로우를 배열로 가져옴 BsonArray bufDataList = bufRecvPayload["events"].AsBsonArray; ///DB에 Duplicate를 할 수 있는 bulkwrite를 하기위한 WriteModel형식으로 만듬. ///Bulkwrite를 선택하면서 DB에서 한번에 유지할수있는 경기의 수가 많이 줄어들었다. Dictionary>> bufDataTable = new Dictionary>>(); foreach (BsonDocument item in bufDataList) { BsonDocument bufUpdateValue = new BsonDocument(); ///rfc461Schema에 해당 event의 종류가 들어있음. string bufStatus = item["rfc461Schema"].ToString(); //DB에서 경기를 관리하기 위한 Key로 RequestGameID와 sequenceIndex를 사용. bufUpdateValue.Add("RequestGameID", mGameID); bufUpdateValue.Add(new BsonElement("sequenceIndex", Convert.ToInt32(item["sequenceIndex"]))); var gameFilter = Builders.Filter.Eq(x => x["RequestGameID"], mGameID); var sequanceFilter = Builders.Filter.Eq(x => x["sequenceIndex"], item["sequenceIndex"]); var Parentfilter = Builders.Filter.And(gameFilter, sequanceFilter); ///Value를 eventDocument에 탑재. bufUpdateValue.Add("eventDocument", item); UpdateOneModel updateRaw = new UpdateOneModel( Parentfilter, Builders.Update.Set(x => x["eventDocument"], item) ) { IsUpsert = true }; if (!bufDataTable.ContainsKey(bufStatus)) { bufDataTable.Add(bufStatus, new List>()); } bufDataTable[bufStatus].Add(updateRaw); } foreach (var item in bufDataTable) { UpdateManager.getInstance().mEventDataBase.GetCollection((mIsProgress ? "in_progress_" : "") + item.Key) .BulkWriteAsync(item.Value); } Console.WriteLine(tokenCount + " : " + bufDataList.Count() + " : " + DateTime.Now.ToLongTimeString() + " : " + mReceivedEventToken); tokenCount += 1; Thread.Sleep(500); } catch (Exception ex) { Console.WriteLine(ex.ToString()); isUpdateWork = false; } } //isUpdate가 false로 빠지는 경우 업데이트중인 게임 목록에서 해당 게임을 지운다. lock (UpdateManager.getInstance().mWorkerTableLocker) { UpdateManager.getInstance().mUpdateWorkerTable.Remove(this.mGameID); } Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.updateWorkerList(); })); } void updateWorkTest() { while (isUpdateWork) { ThreadPool.QueueUserWorkItem(o => { updateRoutineForTest(); }); Thread.Sleep(1000); } lock (UpdateManager.getInstance().mWorkerTableLocker) { UpdateManager.getInstance().mUpdateWorkerTable.Remove(this.mGameID); } Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.updateWorkerList(); })); } //테스트를위한 업데이트루틴 void updateRoutineForTest() { try { string bufRequestURL = DEFINE.라이엇_이벤트행데이터_REQUEST_URL + mGameID + "/events?paginationToken=" + mReceivedEventToken; BsonDocument bufRecvPayload = BsonDocument.Parse(UpdateManager.getInstance().requestRiotData(bufRequestURL)); ///경기가 끝났음 if (bufRecvPayload.Contains("missingEventsStatus")) { if (bufRecvPayload.Contains("lost_permanently")) { isUpdateWork = false; } else { mReceivedEventToken = bufRecvPayload["nextPageToken"].ToString(); } } else if (bufRecvPayload.Contains("nextPageToken")) { mReceivedEventToken = bufRecvPayload["nextPageToken"].ToString(); } //다음페이지토큰이 들어오지 않는 상황에는 updatework를 종료하도록 유도함. else if (!bufRecvPayload.Contains("nextPageToken")) { isUpdateWork = false; } ///20210516 이 방식대로 데이터를 쌓게 되면 데이터를 파싱하는데 너무 복잡하고 오래걸림 //UpdateManager.getInstance().mEventDataBase.GetCollection((mIsProgress ? "in_progress_" : "") + "event_raw") // .UpdateOne( // x => x["nextPageToken"] == bufRecvPayload["nextPageToken"], // Builders.Update.Set(x => x["events"], bufRecvPayload["events"]), // new UpdateOptions() { IsUpsert = true } // ); var filterGameID = Builders.Filter.Eq(x => x["RequestGameID"], mGameID); BsonArray bufDataList = bufRecvPayload["events"].AsBsonArray; //var filter = Builders.Filter.Eq("_id", ObjectId.Parse(dbPro.Id)); //var update = Builders.Update.Push("tags", buildBsonArrayFromTags(pro.Tags)); //var result = collection.UpdateOne(filter, update); //if (result.IsModifiedCountAvailable) //{ // if (result.ModifiedCount == 1) // { // return true; // } //} //벌크인서트를 위한 해시테이블 //Dictionary bufBulkInsertTable = new Dictionary(); //foreach (BsonDocument item in bufDataList) //{ // ///이벤트네임을 키값으로 하고 벌크인서트를 위해 데이터를 나눔. // string bufEventName = item["rfc461Schema"].ToString(); // if (!bufBulkInsertTable.ContainsKey(bufEventName)) // { // bufBulkInsertTable.Add(bufEventName, new BsonArray()); // } // bufBulkInsertTable[bufEventName].Add(item); //} // Dictionary>> bulkModel = new Dictionary>>(); // //경기정보를위해 전체정보를 각각의 이벤트에 맞는 콜렉션에 저장함(누락을 막기위해 동기화). // foreach (var item in bufDataList) // { // var bufFilter = Builders.Filter.Eq("RequestGameID", mGameID); // var bufUpdate = Builders.Update.Push("events", item); // UpdateOneModel updateRaw = new UpdateOneModel( // bufFilter, bufUpdate) { IsUpsert = true }; // if (!bulkModel.ContainsKey(item["rfc461Schema"].ToString())) // { // bulkModel.Add(item["rfc461Schema"].ToString(), new List>()); // } // bulkModel[item["rfc461Schema"].ToString()].Add(updateRaw); //// var resultOne = collectionEvent.UpdateMany(bufFilter, bufUpdate, new UpdateOptions() { IsUpsert = true }); // } // foreach (var item in bulkModel) // { // var result = UpdateManager.getInstance().mEventDataBase.GetCollection(item.Key).BulkWrite(item.Value); // } foreach (var item in bufDataList) { var bufFilter = Builders.Filter.Eq("RequestGameID", mGameID); var bufUpdate = Builders.Update.Push("events", item); var collectionEvent = UpdateManager.getInstance().mEventDataBase.GetCollection(item["rfc461Schema"].ToString()); var resultOne = collectionEvent.UpdateOne(bufFilter, bufUpdate, new UpdateOptions() { IsUpsert = true }); } ////로그를위해 전체정보를 eventraws콜렉션에 저장함(비동기). //var collectionAll = UpdateManager.getInstance().mEventDataBase.GetCollection("eventraws"); //var filterAll = Builders.Filter.Eq("RequestGameID", mGameID); //foreach (var item in collection) //{ //} //var updateAll = Builders.Update.Push("events", bufDataList); //var result2 = collectionAll.UpdateManyAsync(filterAll, updateAll, new UpdateOptions() { IsUpsert = true }); Console.WriteLine(tokenCount + " : " + bufDataList.Count() + " : " + DateTime.Now.ToLongTimeString() + " : " + mReceivedEventToken); tokenCount += 1; Thread.Sleep(1000); } catch (Exception ex) { Console.WriteLine(ex.ToString()); isUpdateWork = false; } } } } }