|
|
@@ -0,0 +1,342 @@
|
|
|
+//
|
|
|
+// RAOfflineHandler.m
|
|
|
+// Apex And Drivers
|
|
|
+//
|
|
|
+// Created by Jack on 2018/10/22.
|
|
|
+// Copyright © 2018年 USAI. All rights reserved.
|
|
|
+//
|
|
|
+
|
|
|
+#import "RAOfflineHandler.h"
|
|
|
+#import "NetworkUtils.h"
|
|
|
+#import "ZipArchive.h"
|
|
|
+#import "RADetailBaseModel.h"
|
|
|
+
|
|
|
+#define Lock() dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER)
|
|
|
+#define Unlock() dispatch_semaphore_signal(_lock)
|
|
|
+
|
|
|
+static dispatch_semaphore_t _lock;
|
|
|
+
|
|
|
+@interface RAOfflineHandler () {
|
|
|
+ NSString *_offlineDir;
|
|
|
+ NSString *_offlineTmp;
|
|
|
+}
|
|
|
+
|
|
|
+@property (nonatomic,strong) NSMutableDictionary *detailCache;///< 缓存查看过的Detai
|
|
|
+@property (nonatomic,strong,readonly) NSString *offlineDir; ///< 缓存文件夹
|
|
|
+@property (nonatomic,strong,readonly) NSString *offlineTmp; ///< 临时保存解压文件的文件夹
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation RAOfflineHandler
|
|
|
+
|
|
|
++ (instancetype)defaultHandler {
|
|
|
+ static RAOfflineHandler *handler;
|
|
|
+ static dispatch_once_t token;
|
|
|
+ dispatch_once(&token, ^{
|
|
|
+ handler = [[RAOfflineHandler alloc] init];
|
|
|
+ _lock = dispatch_semaphore_create(1);
|
|
|
+ });
|
|
|
+ return handler;
|
|
|
+}
|
|
|
+
|
|
|
+- (NSString *)offlineDir {
|
|
|
+ if (!_offlineDir) {
|
|
|
+
|
|
|
+ NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
|
|
|
+ _offlineDir = [cachePath stringByAppendingPathComponent:@"offline"];
|
|
|
+
|
|
|
+ NSFileManager *fm = [NSFileManager defaultManager];
|
|
|
+ BOOL isDir = YES;
|
|
|
+ if ([fm fileExistsAtPath:_offlineDir isDirectory:&isDir] && isDir) {
|
|
|
+
|
|
|
+ } else {
|
|
|
+ [fm createDirectoryAtPath:_offlineDir withIntermediateDirectories:NO attributes:nil error:nil];
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ return _offlineDir;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+- (NSString *)offlineTmp {
|
|
|
+ if (!_offlineTmp) {
|
|
|
+
|
|
|
+ _offlineTmp = [self.offlineDir stringByAppendingPathComponent:@"tmp"];
|
|
|
+
|
|
|
+ NSFileManager *fm = [NSFileManager defaultManager];
|
|
|
+ BOOL isDir = YES;
|
|
|
+ if ([fm fileExistsAtPath:_offlineDir isDirectory:&isDir] && isDir) {
|
|
|
+
|
|
|
+ } else {
|
|
|
+ [fm createDirectoryAtPath:_offlineDir withIntermediateDirectories:NO attributes:nil error:nil];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return _offlineTmp;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 向服务器请求离线数据
|
|
|
+ */
|
|
|
+- (void)downloadOfflineData {
|
|
|
+
|
|
|
+ dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
|
|
+
|
|
|
+ NSMutableDictionary *params = [NSMutableDictionary dictionary];
|
|
|
+ [NetworkUtils downloadFileOffset:0 Param:params from:@"" method:@"POST" toPath:self.offlineDir progressHandler:^(NSURLSessionTask *task, double progress) {
|
|
|
+
|
|
|
+ } completionHandler:^(NSMutableDictionary *result) {
|
|
|
+
|
|
|
+ int rs = [[result objectForKey:@"result"] intValue];
|
|
|
+ NSString *path = [result objectForKey:@"path"];
|
|
|
+ if (rs == RESULT_TRUE) {
|
|
|
+ [self handleDownloadFile:path];
|
|
|
+ }
|
|
|
+
|
|
|
+ }];
|
|
|
+
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 加载沙盒中的数据
|
|
|
+ * @param path 沙盒数据路径
|
|
|
+ */
|
|
|
+- (NSDictionary *)_loadCacheData:(NSString *)path {
|
|
|
+ if (path == nil || path.length == 0) {
|
|
|
+ return nil;
|
|
|
+ }
|
|
|
+
|
|
|
+ NSData *data = [NSData dataWithContentsOfFile:path];
|
|
|
+ NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
|
|
|
+ return dic;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 处理下载的压缩文件
|
|
|
+ * @param path 压缩文件路径
|
|
|
+ */
|
|
|
+- (void)handleDownloadFile:(NSString *)path {
|
|
|
+
|
|
|
+ Lock();
|
|
|
+
|
|
|
+ // 解压
|
|
|
+ BOOL zipRes = [self _unzipOfflineZip:path toDir:self.offlineTmp];
|
|
|
+ if (zipRes) {
|
|
|
+ NSFileManager *fm = [NSFileManager defaultManager];
|
|
|
+
|
|
|
+ // 删除旧数据,除了tmp
|
|
|
+ NSArray<NSString *> *items = [fm contentsOfDirectoryAtPath:self.offlineDir error:nil];
|
|
|
+ [items enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
|
+ if (![obj isEqualToString:self.offlineTmp]) {
|
|
|
+ [fm removeItemAtPath:obj error:nil];
|
|
|
+ }
|
|
|
+ }];
|
|
|
+
|
|
|
+ // 将tmp中解压数据移出来
|
|
|
+ items = [fm contentsOfDirectoryAtPath:self.offlineTmp error:nil];
|
|
|
+ [items enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
|
+
|
|
|
+ NSString *lastComponent = [obj lastPathComponent];
|
|
|
+ NSString *to = [self.offlineDir stringByAppendingPathComponent:lastComponent];
|
|
|
+ [fm moveItemAtPath:obj toPath:to error:nil];
|
|
|
+ }];
|
|
|
+
|
|
|
+ // 重置缓存的action
|
|
|
+ [self _resetFinish];
|
|
|
+ }
|
|
|
+
|
|
|
+ Unlock();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 解压离线下载的压缩包
|
|
|
+ * @return 解压是否成功
|
|
|
+ */
|
|
|
+- (BOOL)_unzipOfflineZip:(NSString *)path toDir:(NSString *)to {
|
|
|
+
|
|
|
+ ZipArchive* zip = [[ZipArchive alloc] init];
|
|
|
+ BOOL zipRes = [zip UnzipOpenFile:path Password:@""];
|
|
|
+ if (zipRes) {
|
|
|
+ zipRes = [zip UnzipFileTo:to overWrite:YES];
|
|
|
+ }
|
|
|
+
|
|
|
+ return zipRes;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 重置离线完成的Action,将finished order对应的action的剔除
|
|
|
+ */
|
|
|
+- (void)_resetFinish {
|
|
|
+
|
|
|
+ NSDictionary *homeJson = [self _requestOfflineHome];
|
|
|
+ NSArray<NSDictionary *> *sections = [homeJson objectForKey:@"sections"];
|
|
|
+
|
|
|
+ NSMutableDictionary *finishDic = [NSMutableDictionary dictionary];
|
|
|
+ NSDictionary *finishDicTmp = [self _finishedActions];
|
|
|
+ if (finishDicTmp.count > 0) {
|
|
|
+ [sections enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
|
+
|
|
|
+ NSArray<NSDictionary *> *orders = [obj objectForKey:@"orders"];
|
|
|
+ [orders enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull order, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
|
+
|
|
|
+ NSString *orderId = [order objectForKey:@"orderID"];
|
|
|
+ NSNumber *finish = [finishDicTmp objectForKey:orderId];
|
|
|
+ if (finish) {
|
|
|
+ [finishDic setObject:finish forKey:orderId];
|
|
|
+ }
|
|
|
+ }];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+
|
|
|
+ NSString *finishPath = [self.offlineDir stringByAppendingPathComponent:@"finish"];
|
|
|
+ [finishDic writeToFile:finishPath atomically:NO];
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 加载所有完成的订单Action
|
|
|
+ */
|
|
|
+- (NSDictionary<NSString *, NSNumber *> *)_finishedActions {
|
|
|
+
|
|
|
+ NSString *path = [self.offlineDir stringByAppendingPathComponent:@"finish"];
|
|
|
+ NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:path];
|
|
|
+
|
|
|
+ return dic;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 加载离线首页数据
|
|
|
+ */
|
|
|
+- (NSDictionary *)_requestOfflineHome {
|
|
|
+
|
|
|
+ NSString *homeJsonPath = [self.offlineDir stringByAppendingPathComponent:@"home.json"];
|
|
|
+ NSDictionary *result = [self _loadCacheData:homeJsonPath];
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 加载离线首页数据
|
|
|
+ */
|
|
|
+- (NSDictionary *)requestOfflineHome {
|
|
|
+ Lock();
|
|
|
+
|
|
|
+ NSDictionary *result = [self _requestOfflineHome];
|
|
|
+
|
|
|
+ Unlock();
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 加载离线Detail
|
|
|
+ * @param orderId 订单号
|
|
|
+ * @param type 订单类型,包括 new order 和 processing order
|
|
|
+ */
|
|
|
+- (NSDictionary *)requestOfflineDetailForOrder:(NSString *)orderId withOrderType:(NSInteger)type {
|
|
|
+ Lock();
|
|
|
+
|
|
|
+ NSString *detailJsonPath = [self.offlineDir stringByAppendingPathComponent:[NSString stringWithFormat:@"detail/%@_%ld",orderId,(long)type]];
|
|
|
+ NSMutableDictionary *detailJson = [[self _loadCacheData:detailJsonPath] mutableCopy];
|
|
|
+
|
|
|
+ NSNumber *finish = [self _lastActionIndexForOrder:orderId];
|
|
|
+
|
|
|
+ detailJson = [self filtrateActionFromDetail:detailJson withFinishActions:finish];
|
|
|
+
|
|
|
+ Unlock();
|
|
|
+ return detailJson;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 在detail中过滤订单已经做过的操作
|
|
|
+ * @param detail order detail界面数据
|
|
|
+ * @param finish order最后完成的一个action索引
|
|
|
+ */
|
|
|
+- (NSMutableDictionary *)filtrateActionFromDetail:(NSMutableDictionary *)detail withFinishActions:(NSNumber *)finish {
|
|
|
+
|
|
|
+ if (finish == nil) {
|
|
|
+ return detail;
|
|
|
+ }
|
|
|
+
|
|
|
+ int result = [[detail objectForKey:@"result"] intValue];
|
|
|
+ if (result != RESULT_TRUE) {
|
|
|
+ return detail;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 一层层解开,获取到目标值后z作出修改,然后反向一层层的套回去
|
|
|
+ NSMutableArray<NSDictionary *> *sections = [[detail objectForKey:@"sections"] mutableCopy];
|
|
|
+ [sections enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
|
+
|
|
|
+ NSMutableDictionary *section = [obj mutableCopy];
|
|
|
+ NSMutableArray<NSDictionary *> *values = [[section objectForKey:@"values"] mutableCopy];
|
|
|
+ [values enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
|
+
|
|
|
+ int type = [[obj objectForKey:@"type"] intValue];
|
|
|
+ if (type == RAOrderDetailValueTypeAction) {
|
|
|
+ NSMutableDictionary *mObj = [obj mutableCopy];
|
|
|
+ NSMutableArray<NSDictionary *> *actions = [[mObj objectForKey:@"actions"] mutableCopy];
|
|
|
+ NSMutableArray *rmArr = [NSMutableArray array];
|
|
|
+ [actions enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
|
+
|
|
|
+ NSInteger index = [[obj objectForKey:@"index"] integerValue];
|
|
|
+ if ([finish integerValue] >= index) {
|
|
|
+ [rmArr addObject:obj];
|
|
|
+ }
|
|
|
+
|
|
|
+ }];
|
|
|
+ [actions removeObjectsInArray:rmArr];
|
|
|
+ [mObj setObject:actions forKey:@"actions"];
|
|
|
+ [values replaceObjectAtIndex:idx withObject:mObj];
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ }];
|
|
|
+ [section setObject:values forKey:@"values"];
|
|
|
+ [sections replaceObjectAtIndex:idx withObject:section];
|
|
|
+ }];
|
|
|
+ [detail setObject:sections forKey:@"sections"];
|
|
|
+
|
|
|
+ return detail;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 记载离线Edit Order
|
|
|
+ * @param orderId 订单号
|
|
|
+ * @param actionIndex 操作标记
|
|
|
+ */
|
|
|
+- (NSDictionary *)requestOfflineEditOrder:(NSString *)orderId withAction:(NSInteger)actionIndex {
|
|
|
+ Lock();
|
|
|
+
|
|
|
+ NSString *editJsonPath = [self.offlineDir stringByAppendingPathComponent:[NSString stringWithFormat:@"edit/%@_%ld",orderId,(long)actionIndex]];
|
|
|
+ NSDictionary *editJson = [self _loadCacheData:editJsonPath];
|
|
|
+
|
|
|
+ Unlock();
|
|
|
+ return editJson;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 最后一次修改Order的 Action 索引
|
|
|
+ * @param orderId 订单号
|
|
|
+ */
|
|
|
+- (NSNumber *)_lastActionIndexForOrder:(NSString *)orderId {
|
|
|
+
|
|
|
+ NSDictionary *finishDic = [self _finishedActions];
|
|
|
+ NSNumber *finish = [finishDic objectForKey:orderId];
|
|
|
+
|
|
|
+ return finish;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 最后一次修改Order的 Action 索引
|
|
|
+ * @param orderId 订单号
|
|
|
+ */
|
|
|
+- (NSNumber *)lastActionIndexForOrder:(NSString *)orderId {
|
|
|
+ Lock();
|
|
|
+
|
|
|
+ NSDictionary *finishDic = [self _finishedActions];
|
|
|
+ NSNumber *finish = [finishDic objectForKey:orderId];
|
|
|
+
|
|
|
+ Unlock();
|
|
|
+ return finish;
|
|
|
+}
|
|
|
+
|
|
|
+@end
|