427 lines
16 KiB
C#
427 lines
16 KiB
C#
using Best.HTTP;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
using UVC.Extension;
|
|
using UVC.Pool;
|
|
using UVC.UI.Modal;
|
|
using UVC.Util;
|
|
|
|
namespace UVC.Factory.Playback.UI
|
|
{
|
|
/// <summary>
|
|
/// 재생 목록의 각 아이템(1시간 단위 파일)을 나타내는 UI 컴포넌트입니다.
|
|
/// 다운로드, 삭제, 선택 등 다양한 상태를 관리합니다.
|
|
///
|
|
/// <para>샘플 사용법:</para>
|
|
/// <code>
|
|
/// // 1. 풀에서 아이템 생성 및 초기화
|
|
/// var itemData = new UIPlaybackListItemData {
|
|
/// date = "2024-07-29",
|
|
/// time = "13",
|
|
/// zipFileName = "2024-07-29_13.zip",
|
|
/// sqlFileName = "2024-07-29_13.sql"
|
|
/// };
|
|
/// var item = UIPlaybackListItem.CreateFromPool(parentTransform);
|
|
/// item.Init(itemData);
|
|
///
|
|
/// // 2. 선택 이벤트 등록
|
|
/// item.OnSelect = (data, selected) => {
|
|
/// Debug.Log($"{data.time}시 아이템이 {(selected ? "선택됨" : "선택 해제됨")}");
|
|
/// };
|
|
///
|
|
/// // 3. 상태 변경 이벤트 등록
|
|
/// item.OnChangeStatus = (data, status) => {
|
|
/// Debug.Log($"{data.time}시 아이템 상태: {status}");
|
|
/// };
|
|
///
|
|
/// // 4. 다운로드 버튼 클릭(직접 호출 예시)
|
|
/// // item.SendMessage("onClickDownload");
|
|
/// </code>
|
|
/// </summary>
|
|
public enum UIPlaybackListItemStatus
|
|
{
|
|
/// <summary>기본 상태(다운로드 전)</summary>
|
|
Default,
|
|
/// <summary>다운로드 중</summary>
|
|
Downloading,
|
|
/// <summary>다운로드 완료</summary>
|
|
Downloaded
|
|
}
|
|
|
|
/// <summary>
|
|
/// 재생 목록 아이템의 데이터 구조체입니다.
|
|
/// </summary>
|
|
public class UIPlaybackListItemData
|
|
{
|
|
public string date = ""; // 날짜 (예: "2024-07-29")
|
|
public string time = ""; // 시간 (예: "13")
|
|
public string zipFileName = ""; // ZIP 파일명
|
|
public string sqlFileName = ""; // SQL 파일명
|
|
public UIPlaybackListItemStatus status = UIPlaybackListItemStatus.Default;
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"date:{date}, time:{time}, zipFileName:{zipFileName}, sqlFileName:{sqlFileName}, status:{status}";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 재생 목록의 각 아이템을 관리하는 UI 컴포넌트입니다.
|
|
/// - 풀링, 상태 관리, 다운로드/삭제/중지 버튼, 선택 이벤트 등을 제공합니다.
|
|
/// </summary>
|
|
public class UIPlaybackListItem : UnityEngine.MonoBehaviour, IPointerClickHandler
|
|
{
|
|
// 오브젝트 풀: 아이템을 효율적으로 재사용하기 위한 풀입니다.
|
|
protected static ItemPool<UIPlaybackListItem> pool;
|
|
|
|
/// <summary>
|
|
/// 프리팹 경로(Resources 폴더 기준)
|
|
/// </summary>
|
|
public static readonly string PrefabPath = "Factory/Prefabs/Playback/UIPlaybackListItem";
|
|
|
|
/// <summary>
|
|
/// 풀에서 아이템을 생성(또는 재사용)합니다.
|
|
/// </summary>
|
|
/// <param name="parent">부모 Transform</param>
|
|
/// <returns>UIPlaybackListItem 인스턴스</returns>
|
|
/// <example>
|
|
/// var item = UIPlaybackListItem.CreateFromPool(parentTransform);
|
|
/// </example>
|
|
public static UIPlaybackListItem CreateFromPool(Transform parent)
|
|
{
|
|
if (pool == null)
|
|
{
|
|
GameObject prefab = Resources.Load<GameObject>(PrefabPath);
|
|
pool = new ItemPool<UIPlaybackListItem>(prefab, parent, null);
|
|
}
|
|
return pool.GetItem(true, parent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 활성화된(화면에 보이는) 아이템 목록
|
|
/// </summary>
|
|
public static List<UIPlaybackListItem> ActiveItems
|
|
{
|
|
get
|
|
{
|
|
if (pool != null) return (List<UIPlaybackListItem>)pool.ActiveItems;
|
|
return new List<UIPlaybackListItem>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 다운로드 중인 아이템 목록
|
|
/// </summary>
|
|
public static List<UIPlaybackListItem> DownloadingItems
|
|
{
|
|
get
|
|
{
|
|
return ActiveItems.Where(x => x.Status == UIPlaybackListItemStatus.Downloading).ToList();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모든 아이템을 풀에 반환(비활성화)합니다.
|
|
/// </summary>
|
|
public static void ReleaseAll()
|
|
{
|
|
if (pool != null)
|
|
{
|
|
foreach (var item in pool.ActiveItems) item.Release();
|
|
pool.ReturnAll();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모든 아이템을 풀에 반환하고, 재활용 목록도 비웁니다.
|
|
/// </summary>
|
|
public static void ClearAll()
|
|
{
|
|
if (pool != null)
|
|
{
|
|
foreach (var item in pool.ActiveItems) item.Release();
|
|
pool.ReturnAll();
|
|
pool.ClearRecycledItems();
|
|
}
|
|
}
|
|
|
|
// --- UI 요소들 (Unity 에디터에서 할당) ---
|
|
[SerializeField]
|
|
private Image loadingImage;
|
|
[SerializeField]
|
|
private Image selectedImage;
|
|
[SerializeField]
|
|
private Button downloadButton;
|
|
[SerializeField]
|
|
private Button stopButton;
|
|
[SerializeField]
|
|
private Button deleteButton;
|
|
[SerializeField]
|
|
private TextMeshProUGUI downloadText;
|
|
[SerializeField]
|
|
private TextMeshProUGUI text;
|
|
|
|
/// <summary>
|
|
/// 아이템이 선택될 때 호출되는 콜백 (data, 선택여부)
|
|
/// </summary>
|
|
public Action<UIPlaybackListItemData, bool> OnSelect { get; set; }
|
|
|
|
/// <summary>
|
|
/// 아이템 상태가 변경될 때 호출되는 콜백 (data, 변경된 상태)
|
|
/// </summary>
|
|
public Action<UIPlaybackListItemData, UIPlaybackListItemStatus> OnChangeStatus { get; set; }
|
|
|
|
private UIPlaybackListItemData data;
|
|
|
|
/// <summary>
|
|
/// 이 아이템의 데이터
|
|
/// </summary>
|
|
public UIPlaybackListItemData Data { get => data; }
|
|
|
|
/// <summary>
|
|
/// 아이템의 현재 상태 (Default/Downloading/Downloaded)
|
|
/// </summary>
|
|
public UIPlaybackListItemStatus Status
|
|
{
|
|
get => data.status;
|
|
set
|
|
{
|
|
if (data.status != value)
|
|
{
|
|
data.status = value;
|
|
ChangeStatus();
|
|
if (OnChangeStatus != null) OnChangeStatus.Invoke(Data, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private bool selected = false;
|
|
|
|
/// <summary>
|
|
/// 아이템이 선택되었는지 여부
|
|
/// </summary>
|
|
public bool Selected
|
|
{
|
|
get => selected;
|
|
set
|
|
{
|
|
if (selected != value)
|
|
{
|
|
selected = value;
|
|
selectedImage.gameObject.SetActive(selected);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 다운로드 요청 객체 (다운로드 중일 때만 사용)
|
|
private HTTPRequest? downloadRequest;
|
|
|
|
/// <summary>
|
|
/// 아이템을 초기화합니다. (데이터 바인딩 및 버튼 이벤트 등록)
|
|
/// </summary>
|
|
/// <param name="data">초기화할 데이터</param>
|
|
/// <example>
|
|
/// var item = UIPlaybackListItem.CreateFromPool(parent);
|
|
/// item.Init(data);
|
|
/// </example>
|
|
public void Init(UIPlaybackListItemData data)
|
|
{
|
|
transform.localScale = Vector3.one;
|
|
selected = false;
|
|
selectedImage.gameObject.SetActive(selected);
|
|
|
|
this.data = data;
|
|
text.text = $"{data.time} ~ {int.Parse(data.time) + 1} Hour";
|
|
downloadButton.onClick.AddListener(onClickDownload);
|
|
stopButton.onClick.AddListener(onClickStop);
|
|
deleteButton.onClick.AddListener(onClickDelete);
|
|
ChangeStatus();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 다운로드 버튼 클릭 시 호출됩니다.
|
|
/// - 용량 체크, 동시 다운로드 제한, 다운로드 시작 등 처리
|
|
/// </summary>
|
|
private void onClickDownload()
|
|
{
|
|
if (Status == UIPlaybackListItemStatus.Downloading) return;
|
|
|
|
long freeSize = DriveUtil.GetDriveGigaBytes();
|
|
|
|
Debug.Log($"여유 공간 freeSize:{freeSize} {10 * (DownloadingItems.Count + 1)} {freeSize < 10 * (DownloadingItems.Count + 1)}");
|
|
if (freeSize < 10 * (DownloadingItems.Count + 1))
|
|
{
|
|
Toast.Show("Playback 하나당 10기가바이트의 용량이 필요합니다. 용량을 확보하고 다운로드 하세요.\nEach Playback requires a capacity of 10 gigabytes. Secure the capacity and download it.");
|
|
return;
|
|
}
|
|
|
|
if (DownloadingItems.Count >= 2)
|
|
{
|
|
Toast.Show("한번에 총 2개 아이템만 다운로드가 가능합니다.\nOnly 2 items can be downloaded at a time.");
|
|
//await UIAlertManager.Instance.ShowAlert("Downloading", "한번에 총 2개 아이템만 다운로드가 가능합니다.\nOnly 3 items can be downloaded at a time.");
|
|
return;
|
|
}
|
|
|
|
Status = UIPlaybackListItemStatus.Downloading;
|
|
|
|
// 실제 다운로드 요청 (진행률/완료/에러 콜백 처리)
|
|
downloadRequest = PlaybackService.Instance.ReadyData(data.date, data.time, data.zipFileName,
|
|
(long read, long total, float percent) =>
|
|
{
|
|
Debug.Log($"ReadyData {percent}");
|
|
downloadText.text = $"{read.ToSizeSuffix()}/{total.ToSizeSuffix()} ({(int)(percent * 100)}%)";
|
|
loadingImage.fillAmount = percent;
|
|
},
|
|
(string errorMessage) =>
|
|
{
|
|
Debug.Log($"Complete Data errorMessage:{errorMessage}");
|
|
if (downloadRequest != null) downloadRequest.Clear();
|
|
downloadRequest = null;
|
|
if (errorMessage != null)
|
|
{
|
|
if (errorMessage.ToLower().Contains("abort")) errorMessage = "다운로드를 중지 했습니다.\nThe download has been stopped.";
|
|
Toast.Show(errorMessage);
|
|
//await UIAlertManager.Instance.ShowAlert("Error", errorMessage);
|
|
deleteFile();
|
|
Status = UIPlaybackListItemStatus.Default;
|
|
}
|
|
else
|
|
{
|
|
Status = UIPlaybackListItemStatus.Downloaded;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 다운로드/삭제 시 파일을 실제로 삭제합니다.
|
|
/// </summary>
|
|
private void deleteFile()
|
|
{
|
|
try
|
|
{
|
|
string playbackPath = PlaybackService.PlaybackFolderPath;
|
|
string tempPath = Path.Combine(playbackPath, "temp");
|
|
string datePath = Path.Combine(playbackPath, data.date);
|
|
var fileNameArr = data.zipFileName.Split('.');
|
|
DateTime utcDateTime = DateTimeUtil.Parse(fileNameArr[0], "yyyy-MM-dd_H");
|
|
string utcSqlFileName = utcDateTime.ToString("yyyy-MM-dd_H") + "." + fileNameArr[1];
|
|
string utcZipFileName = utcSqlFileName + "." + fileNameArr[2];
|
|
string utcZipFilePath = Path.Combine(datePath, utcZipFileName);
|
|
string utcSqlFilePath = Path.Combine(datePath, utcSqlFileName);
|
|
System.GC.Collect();
|
|
System.GC.WaitForPendingFinalizers();
|
|
if (File.Exists(utcZipFilePath)) File.Delete(utcZipFilePath);
|
|
if (File.Exists(utcSqlFilePath)) File.Delete(utcSqlFilePath);
|
|
// if (Directory.Exists(tempPath)) Directory.Delete(tempPath, true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogException(ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 다운로드 중지 버튼 클릭 시 호출됩니다.
|
|
/// </summary>
|
|
private void onClickStop()
|
|
{
|
|
if (downloadRequest != null && Status == UIPlaybackListItemStatus.Downloading)
|
|
{
|
|
downloadRequest.Abort();
|
|
downloadRequest.Clear();
|
|
downloadRequest = null;
|
|
deleteFile();
|
|
Status = UIPlaybackListItemStatus.Default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 삭제 버튼 클릭 시 호출됩니다.
|
|
/// </summary>
|
|
private void onClickDelete()
|
|
{
|
|
deleteFile();
|
|
Status = UIPlaybackListItemStatus.Default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 아이템을 클릭하면 선택/해제 상태가 토글됩니다.
|
|
/// </summary>
|
|
/// <param name="eventData">이벤트 데이터</param>
|
|
public void OnPointerClick(PointerEventData eventData)
|
|
{
|
|
Selected = !selected;
|
|
if (OnSelect != null) OnSelect.Invoke(data, Selected);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 풀에 반환될 때 호출 (이벤트 해제 등)
|
|
/// </summary>
|
|
public void Release()
|
|
{
|
|
OnDestroy();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 오브젝트가 파괴될 때 호출 (이벤트 해제)
|
|
/// </summary>
|
|
private void OnDestroy()
|
|
{
|
|
downloadButton.onClick.RemoveListener(onClickDownload);
|
|
stopButton.onClick.RemoveListener(onClickStop);
|
|
deleteButton.onClick.RemoveListener(onClickDelete);
|
|
OnSelect = null;
|
|
OnChangeStatus = null;
|
|
transform.SetParent(null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 상태에 따라 UI를 갱신합니다.
|
|
/// </summary>
|
|
private void ChangeStatus()
|
|
{
|
|
downloadText.text = "";
|
|
if (data.status == UIPlaybackListItemStatus.Default)
|
|
{
|
|
loadingImage.gameObject.SetActive(false);
|
|
downloadButton.gameObject.SetActive(true);
|
|
stopButton.gameObject.SetActive(false);
|
|
deleteButton.gameObject.SetActive(false);
|
|
downloadText.gameObject.SetActive(false);
|
|
}
|
|
else if (data.status == UIPlaybackListItemStatus.Downloading)
|
|
{
|
|
loadingImage.gameObject.SetActive(true);
|
|
loadingImage.SetAlpha(0.2f);
|
|
loadingImage.fillAmount = 0;
|
|
downloadButton.gameObject.SetActive(false);
|
|
stopButton.gameObject.SetActive(true);
|
|
deleteButton.gameObject.SetActive(false);
|
|
downloadText.gameObject.SetActive(true);
|
|
}
|
|
else if (data.status == UIPlaybackListItemStatus.Downloaded)
|
|
{
|
|
loadingImage.gameObject.SetActive(true);
|
|
loadingImage.SetAlpha(1f);
|
|
loadingImage.fillAmount = 1;
|
|
downloadButton.gameObject.SetActive(false);
|
|
stopButton.gameObject.SetActive(false);
|
|
deleteButton.gameObject.SetActive(true);
|
|
downloadText.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|