com.taptap.sdk.cloudsave 4.8.4-beta.0 → 4.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Mobile/Editor/NativeDependencies.xml +2 -2
- package/Mobile/Runtime/TapCloudSaveBridge.cs +159 -22
- package/Runtime/Internal/ITapCloudSaveBridge.cs +2 -0
- package/Runtime/Internal/TapTapCloudSaveInternal.cs +5 -0
- package/Runtime/Public/TapTapCloudSave.cs +6 -1
- package/Standalone/Runtime/TapCloudSaveStandalone.cs +37 -1
- package/package.json +3 -3
- package/link.xml +0 -3
- package/link.xml.meta +0 -7
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
<repositories>
|
|
5
5
|
<repository>https://repo.maven.apache.org/maven2</repository>
|
|
6
6
|
</repositories>
|
|
7
|
-
<androidPackage spec="com.taptap.sdk:tap-cloudsave-unity:4.8.4
|
|
7
|
+
<androidPackage spec="com.taptap.sdk:tap-cloudsave-unity:4.8.4"/>
|
|
8
8
|
</androidPackages>
|
|
9
9
|
<iosPods>
|
|
10
10
|
<sources>
|
|
11
11
|
<source>https://github.com/CocoaPods/Specs.git</source>
|
|
12
12
|
</sources>
|
|
13
|
-
<iosPod addToAllTargets="false" bitcodeEnabled="false" name="
|
|
13
|
+
<iosPod addToAllTargets="false" bitcodeEnabled="false" name="TapTapSDK/CloudSave" version="4.8.4"/>
|
|
14
14
|
</iosPods>
|
|
15
15
|
</dependencies>
|
|
@@ -22,6 +22,10 @@ namespace TapSDK.CloudSave.Mobile
|
|
|
22
22
|
|
|
23
23
|
public static string TDS_CLOUDSAVE_SERVICE_IMPL = "com.taptap.sdk.cloudsave.unity.BridgeCloudSaveServiceImpl";
|
|
24
24
|
|
|
25
|
+
// 全局callback管理
|
|
26
|
+
private readonly List<ITapCloudSaveCallback> callbacks = new List<ITapCloudSaveCallback>();
|
|
27
|
+
private bool hasRegisteredNativeCallback = false;
|
|
28
|
+
|
|
25
29
|
public TapCloudSaveBridge()
|
|
26
30
|
{
|
|
27
31
|
EngineBridge.GetInstance().Register(TDS_CLOUDSAVE_SERVICE_CLZ, TDS_CLOUDSAVE_SERVICE_IMPL);
|
|
@@ -34,47 +38,160 @@ namespace TapSDK.CloudSave.Mobile
|
|
|
34
38
|
|
|
35
39
|
public void RegisterCloudSaveCallback(ITapCloudSaveCallback callback)
|
|
36
40
|
{
|
|
37
|
-
TapLog.Log("[TapCloudSaveBridge] RegisterCloudSaveCallback called
|
|
38
|
-
|
|
41
|
+
TapLog.Log("[TapCloudSaveBridge] RegisterCloudSaveCallback called with global callback management");
|
|
42
|
+
|
|
43
|
+
if (callback != null)
|
|
44
|
+
{
|
|
45
|
+
// 添加到全局callback列表
|
|
46
|
+
if (!callbacks.Contains(callback))
|
|
47
|
+
{
|
|
48
|
+
callbacks.Add(callback);
|
|
49
|
+
TapLog.Log($"[TapCloudSaveBridge] Added callback. Total callbacks: {callbacks.Count}");
|
|
50
|
+
}
|
|
51
|
+
else
|
|
52
|
+
{
|
|
53
|
+
TapLog.Log("[TapCloudSaveBridge] Callback already registered");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 初始化注册原生callback(只注册一次)
|
|
57
|
+
InitRegisterNativeCallback();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public void UnregisterCloudSaveCallback(ITapCloudSaveCallback callback)
|
|
62
|
+
{
|
|
63
|
+
TapLog.Log("[TapCloudSaveBridge] UnregisterCloudSaveCallback called");
|
|
64
|
+
|
|
65
|
+
if (callback != null)
|
|
66
|
+
{
|
|
67
|
+
if (callbacks.Contains(callback))
|
|
68
|
+
{
|
|
69
|
+
callbacks.Remove(callback);
|
|
70
|
+
TapLog.Log($"[TapCloudSaveBridge] Removed callback. Remaining callbacks: {callbacks.Count}");
|
|
71
|
+
|
|
72
|
+
// 当没有callback时,取消注册原生callback
|
|
73
|
+
if (callbacks.Count == 0 && hasRegisteredNativeCallback)
|
|
74
|
+
{
|
|
75
|
+
var command = new Command.Builder()
|
|
76
|
+
.Service(TAP_CLOUDSAVE_SERVICE)
|
|
77
|
+
.Method("unregisterCloudSaveCallback")
|
|
78
|
+
.Callback(false)
|
|
79
|
+
.OnceTime(false);
|
|
80
|
+
EngineBridge.GetInstance().CallHandler(command.CommandBuilder());
|
|
81
|
+
hasRegisteredNativeCallback = false;
|
|
82
|
+
TapLog.Log("[TapCloudSaveBridge] Unregistered native callback");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else
|
|
86
|
+
{
|
|
87
|
+
TapLog.Log("[TapCloudSaveBridge] Callback not found");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private void InitRegisterNativeCallback()
|
|
93
|
+
{
|
|
94
|
+
if (hasRegisteredNativeCallback)
|
|
95
|
+
{
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
hasRegisteredNativeCallback = true;
|
|
99
|
+
|
|
100
|
+
var command = new Command.Builder()
|
|
39
101
|
.Service(TAP_CLOUDSAVE_SERVICE)
|
|
40
102
|
.Method("registerCloudSaveCallback")
|
|
41
103
|
.Callback(true)
|
|
42
|
-
.OnceTime(
|
|
43
|
-
|
|
104
|
+
.OnceTime(false);
|
|
105
|
+
|
|
106
|
+
EngineBridge.GetInstance().CallHandler(command.CommandBuilder(), (response) =>
|
|
44
107
|
{
|
|
45
|
-
|
|
108
|
+
TapLog.Log($"[TapCloudSaveBridge] Native callback received: code={response.code}, content={response.content}");
|
|
109
|
+
|
|
110
|
+
if (callbacks.Count == 0) return;
|
|
46
111
|
|
|
47
112
|
try
|
|
48
113
|
{
|
|
49
|
-
|
|
50
|
-
{
|
|
51
|
-
callback.OnResult(-1);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
114
|
+
int resultCode = -1;
|
|
54
115
|
|
|
55
|
-
|
|
56
|
-
|
|
116
|
+
// 修复:始终尝试解析content中的resultCode,不要因为response.code失败就跳过
|
|
117
|
+
// iOS端会在TapSDKResult.content中传递真实的错误码(如300001 NEED_LOGIN)
|
|
118
|
+
if (!string.IsNullOrEmpty(response.content))
|
|
57
119
|
{
|
|
58
|
-
|
|
59
|
-
if (resultCode != null)
|
|
120
|
+
try
|
|
60
121
|
{
|
|
61
|
-
|
|
122
|
+
var result = JsonConvert.DeserializeObject<TapEngineBridgeResult>(response.content);
|
|
123
|
+
if (result != null && !string.IsNullOrEmpty(result.content))
|
|
124
|
+
{
|
|
125
|
+
// 尝试解析为字典(iOS格式:{"resultCode": 300001})
|
|
126
|
+
try
|
|
127
|
+
{
|
|
128
|
+
var dataDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(result.content);
|
|
129
|
+
if (dataDict != null && dataDict.ContainsKey("resultCode"))
|
|
130
|
+
{
|
|
131
|
+
resultCode = SafeDictionary.GetValue<int>(dataDict, "resultCode", -1);
|
|
132
|
+
TapLog.Log($"[TapCloudSaveBridge] Parsed resultCode from dictionary: {resultCode}");
|
|
133
|
+
}
|
|
134
|
+
else
|
|
135
|
+
{
|
|
136
|
+
// 尝试直接解析为int(兼容旧格式)
|
|
137
|
+
resultCode = JsonConvert.DeserializeObject<int>(result.content);
|
|
138
|
+
TapLog.Log($"[TapCloudSaveBridge] Parsed resultCode directly: {resultCode}");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch
|
|
142
|
+
{
|
|
143
|
+
// 如果无法解析为字典,尝试直接解析为int
|
|
144
|
+
resultCode = JsonConvert.DeserializeObject<int>(result.content);
|
|
145
|
+
TapLog.Log($"[TapCloudSaveBridge] Parsed resultCode as int: {resultCode}");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
62
148
|
}
|
|
63
|
-
|
|
149
|
+
catch (Exception parseEx)
|
|
64
150
|
{
|
|
65
|
-
|
|
151
|
+
TapLog.Error($"[TapCloudSaveBridge] Failed to parse TapEngineBridgeResult: {parseEx.Message}, using -1");
|
|
152
|
+
resultCode = -1;
|
|
66
153
|
}
|
|
67
154
|
}
|
|
68
155
|
else
|
|
69
156
|
{
|
|
70
|
-
|
|
157
|
+
TapLog.Error("[TapCloudSaveBridge] Response content is null or empty, using -1");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
TapLog.Log($"[TapCloudSaveBridge] Final resultCode: {resultCode}");
|
|
161
|
+
|
|
162
|
+
// 通知所有已注册的callback
|
|
163
|
+
foreach (var callback in callbacks)
|
|
164
|
+
{
|
|
165
|
+
try
|
|
166
|
+
{
|
|
167
|
+
callback?.OnResult(resultCode);
|
|
168
|
+
}
|
|
169
|
+
catch (Exception e)
|
|
170
|
+
{
|
|
171
|
+
TapLog.Log($"[TapCloudSaveBridge] Error in callback execution: {e.Message}");
|
|
172
|
+
}
|
|
71
173
|
}
|
|
72
174
|
}
|
|
73
175
|
catch (Exception e)
|
|
74
176
|
{
|
|
75
|
-
callback.
|
|
177
|
+
TapLog.Log($"[TapCloudSaveBridge] Error processing native callback: {e.Message}");
|
|
178
|
+
|
|
179
|
+
// 即使解析错误,也要通知所有callback
|
|
180
|
+
foreach (var callback in callbacks)
|
|
181
|
+
{
|
|
182
|
+
try
|
|
183
|
+
{
|
|
184
|
+
callback?.OnResult(-1);
|
|
185
|
+
}
|
|
186
|
+
catch (Exception callbackException)
|
|
187
|
+
{
|
|
188
|
+
TapLog.Log($"[TapCloudSaveBridge] Error in callback execution during error handling: {callbackException.Message}");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
76
191
|
}
|
|
77
192
|
});
|
|
193
|
+
|
|
194
|
+
TapLog.Log("[TapCloudSaveBridge] Initialized native callback registration");
|
|
78
195
|
}
|
|
79
196
|
|
|
80
197
|
public Task<ArchiveData> CreateArchive(ArchiveMetadata metadata, string archiveFilePath, string archiveCoverPath)
|
|
@@ -268,58 +385,78 @@ namespace TapSDK.CloudSave.Mobile
|
|
|
268
385
|
|
|
269
386
|
public Task<List<ArchiveData>> GetArchiveList()
|
|
270
387
|
{
|
|
271
|
-
TapLog.Log("[TapCloudSaveBridge] GetArchiveList called
|
|
388
|
+
TapLog.Log("[TapCloudSaveBridge] 🚀 GetArchiveList called - Starting archive list request");
|
|
389
|
+
TapLog.Log($"[TapCloudSaveBridge] 📋 Service: {TAP_CLOUDSAVE_SERVICE}, Method: getArchiveList");
|
|
390
|
+
|
|
272
391
|
var taskSource = new TaskCompletionSource<List<ArchiveData>>();
|
|
273
|
-
|
|
392
|
+
var command = new Command.Builder()
|
|
274
393
|
.Service(TAP_CLOUDSAVE_SERVICE)
|
|
275
394
|
.Method("getArchiveList")
|
|
276
395
|
.Callback(true)
|
|
277
396
|
.OnceTime(true)
|
|
278
|
-
.CommandBuilder()
|
|
397
|
+
.CommandBuilder();
|
|
398
|
+
|
|
399
|
+
TapLog.Log($"[TapCloudSaveBridge] 📤 Sending command to iOS: {command.ToJSON()}");
|
|
400
|
+
|
|
401
|
+
EngineBridge.GetInstance().CallHandler(command, (response) =>
|
|
279
402
|
{
|
|
403
|
+
TapLog.Log($"[TapCloudSaveBridge] 📥 Received response from iOS - Code: {response.code}, Content length: {response.content?.Length ?? 0}");
|
|
404
|
+
|
|
280
405
|
try
|
|
281
406
|
{
|
|
282
407
|
if (response.code != Result.RESULT_SUCCESS || string.IsNullOrEmpty(response.content))
|
|
283
408
|
{
|
|
409
|
+
TapLog.Log($"[TapCloudSaveBridge] ❌ Response failed - Code: {response.code}, Content: {response.content}");
|
|
284
410
|
taskSource.TrySetException(new TapException(-1, "Failed to get archive list: code="+response.code + " content="+response.content));
|
|
285
411
|
return;
|
|
286
412
|
}
|
|
287
413
|
|
|
414
|
+
TapLog.Log($"[TapCloudSaveBridge] 🔍 Parsing response content: {response.content.Substring(0, Math.Min(200, response.content.Length))}...");
|
|
415
|
+
|
|
288
416
|
var result = JsonConvert.DeserializeObject<TapEngineBridgeResult>(response.content);
|
|
289
417
|
if (result != null && result.code == TapEngineBridgeResult.RESULT_SUCCESS)
|
|
290
418
|
{
|
|
419
|
+
TapLog.Log($"[TapCloudSaveBridge] ✅ Bridge result successful, parsing archive list content: {result.content?.Substring(0, Math.Min(100, result.content?.Length ?? 0))}...");
|
|
420
|
+
|
|
291
421
|
var archiveList = JsonConvert.DeserializeObject<List<ArchiveData>>(result.content);
|
|
292
422
|
if (archiveList != null)
|
|
293
423
|
{
|
|
424
|
+
TapLog.Log($"[TapCloudSaveBridge] 🎉 Successfully parsed {archiveList.Count} archives, completing task");
|
|
294
425
|
taskSource.TrySetResult(archiveList);
|
|
295
426
|
}
|
|
296
427
|
else
|
|
297
428
|
{
|
|
429
|
+
TapLog.Log($"[TapCloudSaveBridge] ❌ Failed to deserialize archive list from content: {result.content}");
|
|
298
430
|
taskSource.TrySetException(new TapException(-1, "json convert failed: content="+result.content));
|
|
299
431
|
}
|
|
300
432
|
}
|
|
301
433
|
else
|
|
302
434
|
{
|
|
435
|
+
TapLog.Log($"[TapCloudSaveBridge] ❌ Bridge result failed - Code: {result?.code}, Content: {result?.content}");
|
|
303
436
|
try
|
|
304
437
|
{
|
|
305
438
|
var errorResponse = JsonConvert.DeserializeObject<ErrorResponse>(result.content);
|
|
306
439
|
if (errorResponse != null)
|
|
307
440
|
{
|
|
441
|
+
TapLog.Log($"[TapCloudSaveBridge] 📋 Parsed error response - Code: {errorResponse.ErrorCode}, Message: {errorResponse.ErrorMessage}");
|
|
308
442
|
taskSource.TrySetException(new TapException(errorResponse.ErrorCode, errorResponse.ErrorMessage));
|
|
309
443
|
}
|
|
310
444
|
else
|
|
311
445
|
{
|
|
446
|
+
TapLog.Log("[TapCloudSaveBridge] ❌ Failed to parse error response, raw content: " + response.content);
|
|
312
447
|
taskSource.TrySetException(new TapException(-1, "Failed to get archive list: content="+response.content));
|
|
313
448
|
}
|
|
314
449
|
}
|
|
315
450
|
catch (Exception e)
|
|
316
451
|
{
|
|
452
|
+
TapLog.Log($"[TapCloudSaveBridge] ❌ Exception while parsing error response: {e.Message}");
|
|
317
453
|
taskSource.TrySetException(new TapException(-1, "Failed to get archive list: content="+response.content));
|
|
318
454
|
}
|
|
319
455
|
}
|
|
320
456
|
}
|
|
321
457
|
catch (Exception e)
|
|
322
458
|
{
|
|
459
|
+
TapLog.Log($"[TapCloudSaveBridge] ❌ Top-level exception while processing response: {e.Message}");
|
|
323
460
|
taskSource.TrySetException(new TapException(-1, "Failed to get archive list: error=" + e.Message + ", content=" + response.content));
|
|
324
461
|
}
|
|
325
462
|
});
|
|
@@ -11,6 +11,8 @@ namespace TapSDK.CloudSave.Internal
|
|
|
11
11
|
|
|
12
12
|
void RegisterCloudSaveCallback(ITapCloudSaveCallback callback);
|
|
13
13
|
|
|
14
|
+
void UnregisterCloudSaveCallback(ITapCloudSaveCallback callback);
|
|
15
|
+
|
|
14
16
|
Task<ArchiveData> CreateArchive(ArchiveMetadata metadata, string archiveFilePath, string archiveCoverPath);
|
|
15
17
|
|
|
16
18
|
Task<ArchiveData> UpdateArchive(string archiveUuid, ArchiveMetadata metadata, string archiveFilePath, string archiveCoverPath);
|
|
@@ -26,6 +26,11 @@ namespace TapSDK.CloudSave.Internal
|
|
|
26
26
|
Bridge?.RegisterCloudSaveCallback(callback);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
internal static void UnregisterCloudSaveCallback(ITapCloudSaveCallback callback)
|
|
30
|
+
{
|
|
31
|
+
Bridge?.UnregisterCloudSaveCallback(callback);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
internal static Task<ArchiveData> CreateArchive(ArchiveMetadata metadata, string archiveFilePath, string archiveCoverPath) =>
|
|
30
35
|
Bridge?.CreateArchive(metadata, archiveFilePath, archiveCoverPath);
|
|
31
36
|
|
|
@@ -7,13 +7,18 @@ namespace TapSDK.CloudSave
|
|
|
7
7
|
{
|
|
8
8
|
public class TapTapCloudSave
|
|
9
9
|
{
|
|
10
|
-
public static readonly string Version = "4.8.4
|
|
10
|
+
public static readonly string Version = "4.8.4";
|
|
11
11
|
|
|
12
12
|
public static void RegisterCloudSaveCallback(ITapCloudSaveCallback callback)
|
|
13
13
|
{
|
|
14
14
|
TapTapCloudSaveInternal.RegisterCloudSaveCallback(callback);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
public static void UnregisterCloudSaveCallback(ITapCloudSaveCallback callback)
|
|
18
|
+
{
|
|
19
|
+
TapTapCloudSaveInternal.UnregisterCloudSaveCallback(callback);
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
public static Task<ArchiveData> CreateArchive(ArchiveMetadata metadata, string archiveFilePath, string archiveCoverPath) =>
|
|
18
23
|
TapTapCloudSaveInternal.CreateArchive(metadata, archiveFilePath, archiveCoverPath);
|
|
19
24
|
|
|
@@ -462,7 +462,6 @@ namespace TapSDK.CloudSave.Standalone
|
|
|
462
462
|
|
|
463
463
|
public void RegisterCloudSaveCallback(ITapCloudSaveCallback callback)
|
|
464
464
|
{
|
|
465
|
-
currentSaveCallback?.Clear();
|
|
466
465
|
string seesionId = Guid.NewGuid().ToString();
|
|
467
466
|
const string method = "registerCloudSaveCallback";
|
|
468
467
|
TapCloudSaveTracker.Instance.TrackStart(method, seesionId);
|
|
@@ -474,6 +473,7 @@ namespace TapSDK.CloudSave.Standalone
|
|
|
474
473
|
{
|
|
475
474
|
TapCloudSaveTracker.Instance.TrackSuccess(method, seesionId);
|
|
476
475
|
currentSaveCallback.Add(callback);
|
|
476
|
+
Log($"RegisterCloudSaveCallback: Added callback. Total callbacks: {currentSaveCallback.Count}");
|
|
477
477
|
}
|
|
478
478
|
else
|
|
479
479
|
{
|
|
@@ -482,6 +482,42 @@ namespace TapSDK.CloudSave.Standalone
|
|
|
482
482
|
seesionId,
|
|
483
483
|
errorMessage: "callback has already registered"
|
|
484
484
|
);
|
|
485
|
+
Log("RegisterCloudSaveCallback: Callback already registered");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
public void UnregisterCloudSaveCallback(ITapCloudSaveCallback callback)
|
|
490
|
+
{
|
|
491
|
+
string seesionId = Guid.NewGuid().ToString();
|
|
492
|
+
const string method = "unregisterCloudSaveCallback";
|
|
493
|
+
TapCloudSaveTracker.Instance.TrackStart(method, seesionId);
|
|
494
|
+
|
|
495
|
+
if (currentSaveCallback != null && callback != null)
|
|
496
|
+
{
|
|
497
|
+
if (currentSaveCallback.Contains(callback))
|
|
498
|
+
{
|
|
499
|
+
currentSaveCallback.Remove(callback);
|
|
500
|
+
TapCloudSaveTracker.Instance.TrackSuccess(method, seesionId);
|
|
501
|
+
Log($"UnregisterCloudSaveCallback: Removed callback. Remaining callbacks: {currentSaveCallback.Count}");
|
|
502
|
+
}
|
|
503
|
+
else
|
|
504
|
+
{
|
|
505
|
+
TapCloudSaveTracker.Instance.TrackFailure(
|
|
506
|
+
method,
|
|
507
|
+
seesionId,
|
|
508
|
+
errorMessage: "callback not found"
|
|
509
|
+
);
|
|
510
|
+
Log("UnregisterCloudSaveCallback: Callback not found");
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
else
|
|
514
|
+
{
|
|
515
|
+
TapCloudSaveTracker.Instance.TrackFailure(
|
|
516
|
+
method,
|
|
517
|
+
seesionId,
|
|
518
|
+
errorMessage: "callback or callback list is null"
|
|
519
|
+
);
|
|
520
|
+
Log("UnregisterCloudSaveCallback: Callback or callback list is null");
|
|
485
521
|
}
|
|
486
522
|
}
|
|
487
523
|
|
package/package.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"name": "com.taptap.sdk.cloudsave",
|
|
3
3
|
"displayName": "TapTapSDK CloudSave",
|
|
4
4
|
"description": "TapTapSDK CloudSave",
|
|
5
|
-
"version": "4.8.4
|
|
5
|
+
"version": "4.8.4",
|
|
6
6
|
"unity": "2019.4",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"com.taptap.sdk.core": "4.8.4
|
|
10
|
-
"com.taptap.sdk.login": "4.8.4
|
|
9
|
+
"com.taptap.sdk.core": "4.8.4",
|
|
10
|
+
"com.taptap.sdk.login": "4.8.4"
|
|
11
11
|
}
|
|
12
12
|
}
|
package/link.xml
DELETED