FR2_Duplicate.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. //#define FR2_DEBUG
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using UnityEditor;
  7. using UnityEngine;
  8. using CBParams = System.Collections.Generic.List<System.Collections.Generic.List<string>>;
  9. using Object = UnityEngine.Object;
  10. namespace vietlabs.fr2
  11. {
  12. internal class FR2_DuplicateTree2 : IRefDraw
  13. {
  14. private const float TimeDelayDelete = .5f;
  15. private static readonly FR2_FileCompare fc = new FR2_FileCompare();
  16. private readonly FR2_TreeUI2.GroupDrawer groupDrawer;
  17. private CBParams cacheAssetList;
  18. public bool caseSensitive = false;
  19. private Dictionary<string, List<FR2_Ref>> dicIndex; //index, list
  20. private bool dirty;
  21. private int excludeCount;
  22. private string guidPressDelete;
  23. internal List<FR2_Ref> list;
  24. internal Dictionary<string, FR2_Ref> refs;
  25. public int scanExcludeByIgnoreCount;
  26. public int scanExcludeByTypeCount;
  27. private string searchTerm = "";
  28. private float TimePressDelete;
  29. public FR2_DuplicateTree2(IWindow window)
  30. {
  31. this.window = window;
  32. groupDrawer = new FR2_TreeUI2.GroupDrawer(DrawGroup, DrawAsset);
  33. }
  34. public IWindow window { get; set; }
  35. public bool Draw(Rect rect)
  36. {
  37. return false;
  38. }
  39. public bool DrawLayout()
  40. {
  41. if (dirty)
  42. {
  43. RefreshView(cacheAssetList);
  44. }
  45. if (fc.nChunks2 > 0 && fc.nScaned < fc.nChunks2)
  46. {
  47. Rect rect = GUILayoutUtility.GetRect(1, Screen.width, 18f, 18f);
  48. float p = fc.nScaned / (float) fc.nChunks2;
  49. EditorGUI.ProgressBar(rect, p, string.Format("Scanning {0} / {1}", fc.nScaned, fc.nChunks2));
  50. GUILayout.FlexibleSpace();
  51. return true;
  52. }
  53. if (groupDrawer.hasValidTree)
  54. {
  55. groupDrawer.tree.itemPaddingRight = 60f;
  56. }
  57. groupDrawer.DrawLayout();
  58. DrawHeader();
  59. return false;
  60. }
  61. public int ElementCount()
  62. {
  63. return list == null ? 0 : list.Count;
  64. }
  65. private void DrawAsset(Rect r, string guid)
  66. {
  67. FR2_Ref rf;
  68. if (!refs.TryGetValue(guid, out rf))
  69. {
  70. return;
  71. }
  72. rf.asset.Draw(r, false,
  73. FR2_Setting.GroupMode != FR2_RefDrawer.Mode.Folder,
  74. FR2_Setting.ShowFileSize,
  75. FR2_Setting.s.displayAssetBundleName,
  76. FR2_Setting.s.displayAtlasName,
  77. FR2_Setting.s.showUsedByClassed,
  78. window);
  79. Texture tex = AssetDatabase.GetCachedIcon(rf.asset.assetPath);
  80. if (tex == null)
  81. {
  82. return;
  83. }
  84. Rect drawR = r;
  85. drawR.x = drawR.x + drawR.width; // (groupDrawer.TreeNoScroll() ? 60f : 70f) ;
  86. drawR.width = 40f;
  87. drawR.y += 1;
  88. drawR.height -= 2;
  89. if (GUI.Button(drawR, "Use", EditorStyles.miniButton))
  90. {
  91. if (FR2_Export.IsMergeProcessing)
  92. {
  93. Debug.LogWarning("Previous merge is processing");
  94. }
  95. else
  96. {
  97. //AssetDatabase.SaveAssets();
  98. //EditorGUIUtility.systemCopyBuffer = rf.asset.guid;
  99. //EditorGUIUtility.systemCopyBuffer = rf.asset.guid;
  100. // Debug.Log("guid: " + rf.asset.guid + " systemCopyBuffer " + EditorGUIUtility.systemCopyBuffer);
  101. int index = rf.index;
  102. Selection.objects = list.Where(x => x.index == index)
  103. .Select(x => FR2_Unity.LoadAssetAtPath<Object>(x.asset.assetPath)).ToArray();
  104. FR2_Export.MergeDuplicate(rf.asset.guid);
  105. }
  106. }
  107. if (rf.asset.UsageCount() > 0)
  108. {
  109. return;
  110. }
  111. drawR.x -= 25;
  112. drawR.width = 20;
  113. if (wasPreDelete(guid))
  114. {
  115. Color col = GUI.color;
  116. GUI.color = Color.red;
  117. if (GUI.Button(drawR, "X", EditorStyles.miniButton))
  118. {
  119. guidPressDelete = null;
  120. AssetDatabase.DeleteAsset(rf.asset.assetPath);
  121. }
  122. GUI.color = col;
  123. window.WillRepaint = true;
  124. }
  125. else
  126. {
  127. if (GUI.Button(drawR, "X", EditorStyles.miniButton))
  128. {
  129. guidPressDelete = guid;
  130. TimePressDelete = Time.realtimeSinceStartup;
  131. window.WillRepaint = true;
  132. }
  133. }
  134. }
  135. private bool wasPreDelete(string guid)
  136. {
  137. if (guidPressDelete == null || guid != guidPressDelete)
  138. {
  139. return false;
  140. }
  141. if (Time.realtimeSinceStartup - TimePressDelete < TimeDelayDelete)
  142. {
  143. return true;
  144. }
  145. guidPressDelete = null;
  146. return false;
  147. }
  148. private void DrawGroup(Rect r, string label, int childCount)
  149. {
  150. // GUI.Label(r, label + " (" + childCount + ")", EditorStyles.boldLabel);
  151. FR2_Asset asset = dicIndex[label][0].asset;
  152. Texture tex = AssetDatabase.GetCachedIcon(asset.assetPath);
  153. Rect rect = r;
  154. if (tex != null)
  155. {
  156. rect.width = 16f;
  157. GUI.DrawTexture(rect, tex);
  158. }
  159. rect = r;
  160. rect.xMin += 16f;
  161. GUI.Label(rect, asset.assetName, EditorStyles.boldLabel);
  162. rect = r;
  163. rect.xMin += rect.width - 50f;
  164. GUI.Label(rect, FR2_Helper.GetfileSizeString(asset.fileSize), EditorStyles.miniLabel);
  165. rect = r;
  166. rect.xMin += rect.width - 70f;
  167. GUI.Label(rect, childCount.ToString(), EditorStyles.miniLabel);
  168. rect = r;
  169. rect.xMin += rect.width - 70f;
  170. }
  171. // private List<FR2_DuplicateFolder> duplicated;
  172. public void Reset(CBParams assetList)
  173. {
  174. fc.Reset(assetList, OnUpdateView, RefreshView);
  175. }
  176. private void OnUpdateView(CBParams assetList) { }
  177. public bool isExclueAnyItem()
  178. {
  179. return excludeCount > 0 || scanExcludeByTypeCount > 0;
  180. }
  181. public bool isExclueAnyItemByIgnoreFolder()
  182. {
  183. return scanExcludeByIgnoreCount > 0;
  184. }
  185. // void OnActive
  186. private void RefreshView(CBParams assetList)
  187. {
  188. cacheAssetList = assetList;
  189. dirty = false;
  190. list = new List<FR2_Ref>();
  191. refs = new Dictionary<string, FR2_Ref>();
  192. dicIndex = new Dictionary<string, List<FR2_Ref>>();
  193. if (assetList == null)
  194. {
  195. return;
  196. }
  197. int minScore = searchTerm.Length;
  198. string term1 = searchTerm;
  199. if (!caseSensitive)
  200. {
  201. term1 = term1.ToLower();
  202. }
  203. string term2 = term1.Replace(" ", string.Empty);
  204. excludeCount = 0;
  205. for (var i = 0; i < assetList.Count; i++)
  206. {
  207. var lst = new List<FR2_Ref>();
  208. for (var j = 0; j < assetList[i].Count; j++)
  209. {
  210. string guid = AssetDatabase.AssetPathToGUID(assetList[i][j]);
  211. if (string.IsNullOrEmpty(guid))
  212. {
  213. continue;
  214. }
  215. if (refs.ContainsKey(guid))
  216. {
  217. continue;
  218. }
  219. FR2_Asset asset = FR2_Cache.Api.Get(guid);
  220. if (asset == null) continue;
  221. if (!asset.assetPath.StartsWith("Assets/")) continue; // ignore builtin, packages, ...
  222. var fr2 = new FR2_Ref(i, 0, asset, null);
  223. if (FR2_Setting.IsTypeExcluded(fr2.type))
  224. {
  225. excludeCount++;
  226. continue; //skip this one
  227. }
  228. if (string.IsNullOrEmpty(searchTerm))
  229. {
  230. fr2.matchingScore = 0;
  231. list.Add(fr2);
  232. lst.Add(fr2);
  233. refs.Add(guid, fr2);
  234. continue;
  235. }
  236. //calculate matching score
  237. string name1 = fr2.asset.assetName;
  238. if (!caseSensitive)
  239. {
  240. name1 = name1.ToLower();
  241. }
  242. string name2 = name1.Replace(" ", string.Empty);
  243. int score1 = FR2_Unity.StringMatch(term1, name1);
  244. int score2 = FR2_Unity.StringMatch(term2, name2);
  245. fr2.matchingScore = Mathf.Max(score1, score2);
  246. if (fr2.matchingScore > minScore)
  247. {
  248. list.Add(fr2);
  249. lst.Add(fr2);
  250. refs.Add(guid, fr2);
  251. }
  252. }
  253. dicIndex.Add(i.ToString(), lst);
  254. }
  255. ResetGroup();
  256. }
  257. private void ResetGroup()
  258. {
  259. groupDrawer.Reset(list,
  260. rf => rf.asset.guid
  261. , GetGroup, SortGroup);
  262. if (window != null)
  263. {
  264. window.Repaint();
  265. }
  266. }
  267. private string GetGroup(FR2_Ref rf)
  268. {
  269. return rf.index.ToString();
  270. }
  271. private void SortGroup(List<string> groups)
  272. {
  273. // groups.Sort( (item1, item2) =>
  274. // {
  275. // if (item1 == "Others" || item2 == "Selection") return 1;
  276. // if (item2 == "Others" || item1 == "Selection") return -1;
  277. // return item1.CompareTo(item2);
  278. // });
  279. }
  280. public void SetDirty()
  281. {
  282. dirty = true;
  283. }
  284. public void RefreshSort() { }
  285. private void DrawHeader()
  286. {
  287. var text = groupDrawer.hasValidTree ? "Rescan" : "Scan";
  288. if (GUILayout.Button(text))
  289. {
  290. FR2_Cache.onReady -= OnCacheReady;
  291. FR2_Cache.onReady += OnCacheReady;
  292. FR2_Cache.Api.Check4Changes(false);
  293. }
  294. }
  295. private void OnCacheReady()
  296. {
  297. scanExcludeByTypeCount = 0;
  298. Reset(FR2_Cache.Api.ScanSimilar(IgnoreTypeWhenScan, IgnoreFolderWhenScan));
  299. FR2_Cache.onReady -= OnCacheReady;
  300. }
  301. private void IgnoreTypeWhenScan()
  302. {
  303. scanExcludeByTypeCount++;
  304. }
  305. private void IgnoreFolderWhenScan()
  306. {
  307. scanExcludeByIgnoreCount++;
  308. }
  309. }
  310. internal class FR2_FileCompare
  311. {
  312. public static HashSet<FR2_Chunk> HashChunksNotComplete;
  313. internal static int streamClosedCount;
  314. private CBParams cacheList;
  315. public List<FR2_Head> deads = new List<FR2_Head>();
  316. public List<FR2_Head> heads = new List<FR2_Head>();
  317. public int nChunks;
  318. public int nChunks2;
  319. public int nScaned;
  320. public Action<CBParams> OnCompareComplete;
  321. public Action<CBParams> OnCompareUpdate;
  322. private int streamCount;
  323. public void Reset(CBParams list, Action<CBParams> onUpdate, Action<CBParams> onComplete)
  324. {
  325. nChunks = 0;
  326. nScaned = 0;
  327. nChunks2 = 0;
  328. streamCount = streamClosedCount = 0;
  329. HashChunksNotComplete = new HashSet<FR2_Chunk>();
  330. if (heads.Count > 0)
  331. {
  332. for (var i = 0; i < heads.Count; i++)
  333. {
  334. heads[i].CloseChunk();
  335. }
  336. }
  337. deads.Clear();
  338. heads.Clear();
  339. OnCompareUpdate = onUpdate;
  340. OnCompareComplete = onComplete;
  341. if (list.Count <= 0)
  342. {
  343. OnCompareComplete(new CBParams());
  344. return;
  345. }
  346. cacheList = list;
  347. for (var i = 0; i < list.Count; i++)
  348. {
  349. var file = new FileInfo(list[i][0]);
  350. int nChunk = Mathf.CeilToInt(file.Length / (float) FR2_Head.chunkSize);
  351. nChunks2 += nChunk;
  352. }
  353. // for(int i =0;i< list.Count;i++)
  354. // {
  355. // AddHead(list[i]);
  356. // }
  357. AddHead(cacheList[cacheList.Count - 1]);
  358. cacheList.RemoveAt(cacheList.Count - 1);
  359. EditorApplication.update -= ReadChunkAsync;
  360. EditorApplication.update += ReadChunkAsync;
  361. }
  362. public FR2_FileCompare AddHead(List<string> files)
  363. {
  364. if (files.Count < 2)
  365. {
  366. Debug.LogWarning("Something wrong ! head should not contains < 2 elements");
  367. }
  368. var chunkList = new List<FR2_Chunk>();
  369. for (var i = 0; i < files.Count; i++)
  370. {
  371. streamCount++;
  372. // Debug.Log("new stream " + files[i]);
  373. chunkList.Add(new FR2_Chunk
  374. {
  375. file = files[i],
  376. stream = new FileStream(files[i], FileMode.Open, FileAccess.Read),
  377. buffer = new byte[FR2_Head.chunkSize]
  378. });
  379. }
  380. var file = new FileInfo(files[0]);
  381. int nChunk = Mathf.CeilToInt(file.Length / (float) FR2_Head.chunkSize);
  382. heads.Add(new FR2_Head
  383. {
  384. fileSize = file.Length,
  385. currentChunk = 0,
  386. nChunk = nChunk,
  387. chunkList = chunkList
  388. });
  389. nChunks += nChunk;
  390. return this;
  391. }
  392. private bool checkCompleteAllCurFile()
  393. {
  394. return streamClosedCount + HashChunksNotComplete.Count >= streamCount; //-1 for safe
  395. }
  396. private void ReadChunkAsync()
  397. {
  398. bool alive = ReadChunk();
  399. HashChunksNotComplete.RemoveWhere(x => x.stream == null || !x.stream.CanRead);
  400. if (cacheList.Count > 0 && checkCompleteAllCurFile()) //complete all chunk
  401. {
  402. int numCall = FR2_Cache.priority; // - 2;
  403. if (numCall <= 0)
  404. {
  405. numCall = 1;
  406. }
  407. for (var i = 0; i < numCall; i++)
  408. {
  409. if (cacheList.Count <= 0)
  410. {
  411. break;
  412. }
  413. AddHead(cacheList[cacheList.Count - 1]);
  414. cacheList.RemoveAt(cacheList.Count - 1);
  415. }
  416. }
  417. var update = false;
  418. for (int i = heads.Count - 1; i >= 0; i--)
  419. {
  420. FR2_Head h = heads[i];
  421. if (h.isDead)
  422. {
  423. h.CloseChunk();
  424. heads.RemoveAt(i);
  425. if (h.chunkList.Count > 1)
  426. {
  427. update = true;
  428. deads.Add(h);
  429. }
  430. }
  431. }
  432. if (update)
  433. {
  434. Trigger(OnCompareUpdate);
  435. }
  436. if (!alive && cacheList.Count <= 0 && checkCompleteAllCurFile()
  437. ) //&& cacheList.Count <= 0 complete all chunk and cache list empty
  438. {
  439. foreach (FR2_Chunk item in HashChunksNotComplete)
  440. {
  441. if (item.stream != null && item.stream.CanRead)
  442. {
  443. item.stream.Close();
  444. item.stream = null;
  445. }
  446. }
  447. HashChunksNotComplete.Clear();
  448. // Debug.Log("complete ");
  449. nScaned = nChunks;
  450. EditorApplication.update -= ReadChunkAsync;
  451. Trigger(OnCompareComplete);
  452. }
  453. }
  454. private void Trigger(Action<CBParams> cb)
  455. {
  456. if (cb == null)
  457. {
  458. return;
  459. }
  460. CBParams list = deads.Select(item => item.GetFiles()).ToList();
  461. //#if FR2_DEBUG
  462. // Debug.Log("Callback ! " + deads.Count + ":" + heads.Count);
  463. //#endif
  464. cb(list);
  465. }
  466. private bool ReadChunk()
  467. {
  468. var alive = false;
  469. for (var i = 0; i < heads.Count; i++)
  470. {
  471. FR2_Head h = heads[i];
  472. if (h.isDead)
  473. {
  474. Debug.LogWarning("Should never be here : " + h.chunkList[0].file);
  475. continue;
  476. }
  477. nScaned++;
  478. alive = true;
  479. h.ReadChunk();
  480. h.CompareChunk(heads);
  481. break;
  482. }
  483. //if (!alive) return false;
  484. //alive = false;
  485. //for (var i = 0; i < heads.Count; i++)
  486. //{
  487. // var h = heads[i];
  488. // if (h.isDead) continue;
  489. // h.CompareChunk(heads);
  490. // alive |= !h.isDead;
  491. //}
  492. return alive;
  493. }
  494. }
  495. internal class FR2_Head
  496. {
  497. public const int chunkSize = 10240;
  498. public List<FR2_Chunk> chunkList;
  499. public int currentChunk;
  500. public long fileSize;
  501. public int nChunk;
  502. public int size; //last stream read size
  503. public bool isDead
  504. {
  505. get { return currentChunk == nChunk || chunkList.Count == 1; }
  506. }
  507. public List<string> GetFiles()
  508. {
  509. return chunkList.Select(item => item.file).ToList();
  510. }
  511. public void AddToDict(byte b, FR2_Chunk chunk, Dictionary<byte, List<FR2_Chunk>> dict)
  512. {
  513. List<FR2_Chunk> list;
  514. if (!dict.TryGetValue(b, out list))
  515. {
  516. list = new List<FR2_Chunk>();
  517. dict.Add(b, list);
  518. }
  519. list.Add(chunk);
  520. }
  521. public void CloseChunk()
  522. {
  523. for (var i = 0; i < chunkList.Count; i++)
  524. {
  525. // Debug.Log("stream close");
  526. FR2_FileCompare.streamClosedCount++;
  527. chunkList[i].stream.Close();
  528. chunkList[i].stream = null;
  529. }
  530. }
  531. public void ReadChunk()
  532. {
  533. #if FR2_DEBUG
  534. if (currentChunk == 0) Debug.LogWarning("Read <" + chunkList[0].file + "> " + currentChunk + ":" + nChunk);
  535. #endif
  536. if (currentChunk == nChunk)
  537. {
  538. Debug.LogWarning("Something wrong, should dead <" + isDead + ">");
  539. return;
  540. }
  541. int from = currentChunk * chunkSize;
  542. size = (int) Mathf.Min(fileSize - from, chunkSize);
  543. for (var i = 0; i < chunkList.Count; i++)
  544. {
  545. FR2_Chunk chunk = chunkList[i];
  546. chunk.size = size;
  547. chunk.stream.Read(chunk.buffer, 0, size);
  548. }
  549. currentChunk++;
  550. }
  551. public void CompareChunk(List<FR2_Head> heads)
  552. {
  553. int idx = chunkList.Count;
  554. byte[] buffer = chunkList[idx - 1].buffer;
  555. while (--idx >= 0)
  556. {
  557. FR2_Chunk chunk = chunkList[idx];
  558. int diff = FirstDifferentIndex(buffer, chunk.buffer, size);
  559. if (diff == -1)
  560. {
  561. continue;
  562. }
  563. #if FR2_DEBUG
  564. Debug.Log(string.Format(
  565. " --> Different found at : idx={0} diff={1} size={2} chunk={3}",
  566. idx, diff, size, currentChunk));
  567. #endif
  568. byte v = buffer[diff];
  569. var d = new Dictionary<byte, List<FR2_Chunk>>(); //new heads
  570. chunkList.RemoveAt(idx);
  571. FR2_FileCompare.HashChunksNotComplete.Add(chunk);
  572. AddToDict(chunk.buffer[diff], chunk, d);
  573. for (int j = idx - 1; j >= 0; j--)
  574. {
  575. FR2_Chunk tChunk = chunkList[j];
  576. byte tValue = tChunk.buffer[diff];
  577. if (tValue == v)
  578. {
  579. continue;
  580. }
  581. idx--;
  582. FR2_FileCompare.HashChunksNotComplete.Add(tChunk);
  583. chunkList.RemoveAt(j);
  584. AddToDict(tChunk.buffer[diff], tChunk, d);
  585. }
  586. foreach (KeyValuePair<byte, List<FR2_Chunk>> item in d)
  587. {
  588. List<FR2_Chunk> list = item.Value;
  589. if (list.Count == 1)
  590. {
  591. #if FR2_DEBUG
  592. Debug.Log(" --> Dead head found for : " + list[0].file);
  593. #endif
  594. }
  595. else if (list.Count > 1) // 1 : dead head
  596. {
  597. #if FR2_DEBUG
  598. Debug.Log(" --> NEW HEAD : " + list[0].file);
  599. #endif
  600. heads.Add(new FR2_Head
  601. {
  602. nChunk = nChunk,
  603. fileSize = fileSize,
  604. currentChunk = currentChunk - 1,
  605. chunkList = list
  606. });
  607. }
  608. }
  609. }
  610. }
  611. internal static int FirstDifferentIndex(byte[] arr1, byte[] arr2, int maxIndex)
  612. {
  613. for (var i = 0; i < maxIndex; i++)
  614. {
  615. if (arr1[i] != arr2[i])
  616. {
  617. return i;
  618. }
  619. }
  620. return -1;
  621. }
  622. }
  623. internal class FR2_Chunk
  624. {
  625. public byte[] buffer;
  626. public string file;
  627. public long size;
  628. public FileStream stream;
  629. }
  630. }