Skip to content

Commit

Permalink
feat: webdav sync - single task sync (indev)
Browse files Browse the repository at this point in the history
  • Loading branch information
FriesI23 committed Mar 7, 2025
1 parent caf8982 commit e8f9e18
Show file tree
Hide file tree
Showing 9 changed files with 1,189 additions and 104 deletions.
12 changes: 12 additions & 0 deletions lib/extension/webdav_extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:io';

import 'package:simple_webdav_client/dav.dart';
import 'package:simple_webdav_client/utils.dart';

Expand All @@ -25,4 +27,14 @@ extension WebDavResourceExtention on WebDavStdResource {
.first as WebDavStdResourceProp<ResourceTypes>;

bool get isCollection => resourcetype.value?.isCollection ?? false;

void tryToRaiseError({bool raiseError = true, bool raiseStatus = true}) {
if (raiseError) {
final error = this.error;
if (error != null) throw error;
}
if (raiseStatus && status > 300) {
throw HttpException("Http request failed, status=$status", uri: path);
}
}
}
352 changes: 352 additions & 0 deletions lib/model/_app_sync_tasks/webdav_app_sync_models.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,352 @@
// Copyright 2025 Fries_I23
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:io';

import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:simple_webdav_client/dav.dart';
import 'package:simple_webdav_client/error.dart';

import '../../common/types.dart';
import '../../extension/webdav_extensions.dart';
import '../../persistent/local/handler/habit.dart';
import '../../persistent/local/handler/record.dart';
import '../common.dart';
import 'webdav_app_sync_subtasks.dart';

part 'webdav_app_sync_models.g.dart';

abstract class _WebDavAppSyncCellInfo {
Uri? serverPath;
String? eTagFromServer;
String? eTagFromLocal;

bool _includeDirtyMark;
WebDavAppSyncInfoStatus _status;

_WebDavAppSyncCellInfo(
{required bool isDirty, required WebDavAppSyncInfoStatus status})
: _includeDirtyMark = isDirty,
_status = status;

WebDavAppSyncInfoStatus get status => _status;

set status(WebDavAppSyncInfoStatus newStatus) {
if (_status != newStatus) _status = WebDavAppSyncInfoStatus.both;
}

bool get includeDirtyMark => _includeDirtyMark;

void makeDirty() => _includeDirtyMark = true;

bool get isNeedDownload =>
status == WebDavAppSyncInfoStatus.server ||
eTagFromLocal != eTagFromServer;

bool get isNeedUpload =>
status == WebDavAppSyncInfoStatus.local || includeDirtyMark;
}

class WebDavAppSyncHabitInfo extends _WebDavAppSyncCellInfo {
final HabitUUID uuid;

WebDavAppSyncHabitInfo({
required this.uuid,
required super.status,
super.isDirty = false,
});

@override
String toString() => "WebDavAppSyncCellInfo(uuid=$uuid, status=$status, "
"sEtag=$eTagFromServer, cEtag=$eTagFromLocal, spath=$serverPath"
")";
}

class WebDavAppSyncRecordInfo extends _WebDavAppSyncCellInfo {
final HabitUUID parentUUID;
final HabitRecordUUID uuid;

WebDavAppSyncRecordInfo({
required this.parentUUID,
required this.uuid,
required super.status,
super.isDirty = false,
});

@override
String toString() =>
"WebDavAppSyncRecordInfo(uuid=$uuid, puuid=$parentUUID status=$status, "
"sEtag=$eTagFromServer, cEtag=$eTagFromLocal, spath=$serverPath"
")";
}

class WebDavResourceContainer {
final Uri path;
final String? etag;

const WebDavResourceContainer({
required this.path,
this.etag,
});

factory WebDavResourceContainer.fromResource(WebDavStdResource resource,
{Uri? overridePath}) {
assert(resource.error == null);
final getetag = resource.getetag;
if (getetag.error != null) throw getetag.error!;
if (getetag.status != HttpStatus.ok) {
throw WebDavStdResError(
"Resouce ${resource.path}'s dav:getetag status error, "
"prop=${getetag.toDebugString()}");
}
return WebDavResourceContainer(
path: overridePath ?? resource.path,
etag: resource.getetag.value,
);
}

HabitUUID? get habitUUID {
final filename = path.pathSegments.lastOrNull;
if (filename == null || filename.isEmpty) return null;
return reAppSyncHabitFileName.firstMatch(filename)?.group(1);
}

HabitRecordUUID? get recordUUID {
final filename = path.pathSegments.lastOrNull;
if (filename == null || filename.isEmpty) return null;
return reAppSyncRecordFileName.firstMatch(filename)?.group(1);
}

@override
String toString() => 'WebDavResourceContainer(path=$path, etag=<$etag>)';
}

class WebDavSyncRecordKey {
static const String recordDate = 'record_date';
static const String recordType = 'record_type';
static const String recordValue = 'record_value';
static const String createT = 'create_t';
static const String modifyT = 'modify_t';
static const String uuid = 'uuid';
static const String parentUUID = 'parent_uuid';
static const String reason = 'reason';
static const String convertType = '_convert_type';
}

@JsonSerializable(
fieldRename: FieldRename.snake,
includeIfNull: true,
ignoreUnannotated: true)
@CopyWith(skipFields: true)
class WebDavSyncRecordData implements JsonAdaptor {
static const _convertType = 'record_';

@JsonKey(name: WebDavSyncRecordKey.recordDate)
final int? recordDate;
@JsonKey(name: WebDavSyncRecordKey.recordType)
final int? recordType;
@JsonKey(name: WebDavSyncRecordKey.recordValue)
final num? recordValue;
@JsonKey(name: WebDavSyncRecordKey.createT)
final int? createT;
@JsonKey(name: WebDavSyncRecordKey.modifyT)
final int? modifyT;
@JsonKey(name: WebDavSyncRecordKey.uuid)
final HabitRecordUUID? uuid;
@JsonKey(name: WebDavSyncRecordKey.parentUUID)
final HabitUUID? parentUUID;
@JsonKey(name: WebDavSyncRecordKey.reason)
final String? reason;

const WebDavSyncRecordData({
this.recordDate,
this.recordType,
this.recordValue,
this.createT,
this.modifyT,
this.uuid,
this.parentUUID,
this.reason,
});

WebDavSyncRecordData.fromHabitDBCell(RecordDBCell cell)
: recordDate = cell.recordDate,
recordType = cell.recordType,
recordValue = cell.recordValue,
createT = cell.createT,
modifyT = cell.modifyT,
uuid = cell.uuid,
parentUUID = cell.parentUUID,
reason = cell.reason;

factory WebDavSyncRecordData.fromJson(JsonMap json) {
assert(json.remove(WebDavSyncRecordKey.convertType) == _convertType);
return _$WebDavSyncRecordDataFromJson(json);
}

RecordDBCell toRecordDBCell() => RecordDBCell(
recordDate: recordDate,
recordType: recordType,
recordValue: recordValue,
createT: createT,
modifyT: modifyT,
uuid: uuid,
parentUUID: parentUUID,
reason: reason,
);

@override
Map<String, dynamic> toJson() => _$WebDavSyncRecordDataToJson(this)
..[WebDavSyncRecordKey.convertType] = _convertType;

@override
String toString() => "WebDavSyncRecordData${toJson()}";
}

class WebDavSyncHabitKey {
static const String createT = 'create_t';
static const String modifyT = 'modify_t';
static const String type = 'type';
static const String status = 'status';
static const String name = 'name';
static const String desc = 'desc';
static const String color = 'color';
static const String dailyGoal = 'daily_goal';
static const String dailyGoalUnit = 'daily_goal_unit';
static const String dailyGoalExtra = 'daily_goal_extra';
static const String freqType = 'freq_type';
static const String freqCustom = 'freq_custom';
static const String reminder = 'reminder';
static const String reminderQuest = 'reminder_quest';
static const String startDate = 'start_date';
static const String targetDays = 'target_days';
static const String sortPosition = 'sort_position';
static const String convertType = '_convert_type';
}

@JsonSerializable(
fieldRename: FieldRename.snake,
includeIfNull: true,
ignoreUnannotated: true)
@CopyWith(skipFields: true)
class WebDavSyncHabitData implements JsonAdaptor {
static const _convertType = 'habit_';

@JsonKey(name: WebDavSyncHabitKey.createT)
final int? createT;
@JsonKey(name: WebDavSyncHabitKey.modifyT)
final int? modifyT;
@JsonKey(name: WebDavSyncHabitKey.type)
final int? type;
@JsonKey(name: WebDavSyncHabitKey.status)
final int? status;
@JsonKey(name: WebDavSyncHabitKey.name)
final String? name;
@JsonKey(name: WebDavSyncHabitKey.desc)
final String? desc;
@JsonKey(name: WebDavSyncHabitKey.color)
final int? color;
@JsonKey(name: WebDavSyncHabitKey.dailyGoal)
final num? dailyGoal;
@JsonKey(name: WebDavSyncHabitKey.dailyGoalUnit)
final String? dailyGoalUnit;
@JsonKey(name: WebDavSyncHabitKey.dailyGoalExtra)
final num? dailyGoalExtra;
@JsonKey(name: WebDavSyncHabitKey.freqType)
final int? freqType;
@JsonKey(name: WebDavSyncHabitKey.freqCustom)
final String? freqCustom;
@JsonKey(name: WebDavSyncHabitKey.reminder)
final String? reminder;
@JsonKey(name: WebDavSyncHabitKey.reminderQuest)
final String? reminderQuest;
@JsonKey(name: WebDavSyncHabitKey.startDate)
final int? startDate;
@JsonKey(name: WebDavSyncHabitKey.targetDays)
final int? targetDays;
@JsonKey(name: WebDavSyncHabitKey.sortPosition)
final HabitSortPostion? sortPostion;

const WebDavSyncHabitData({
this.createT,
this.modifyT,
this.type,
this.status,
this.name,
this.desc,
this.color,
this.dailyGoal,
this.dailyGoalUnit,
this.dailyGoalExtra,
this.freqType,
this.freqCustom,
this.reminder,
this.reminderQuest,
this.startDate,
this.targetDays,
this.sortPostion,
});

WebDavSyncHabitData.fromHabitDBCell(HabitDBCell cell)
: createT = cell.createT,
modifyT = cell.modifyT,
type = cell.type,
status = cell.status,
name = cell.name,
desc = cell.desc,
color = cell.color,
dailyGoal = cell.dailyGoal,
dailyGoalUnit = cell.dailyGoalUnit,
dailyGoalExtra = cell.dailyGoalExtra,
freqType = cell.freqType,
freqCustom = cell.freqCustom,
reminder = cell.remindCustom,
reminderQuest = cell.remindQuestion,
startDate = cell.startDate,
targetDays = cell.targetDays,
sortPostion = cell.sortPosition;

factory WebDavSyncHabitData.fromJson(JsonMap json) {
assert(json.remove(WebDavSyncHabitKey.convertType) == _convertType);
return _$WebDavSyncHabitDataFromJson(json);
}

HabitDBCell toHabitDBCell() => HabitDBCell(
createT: createT,
modifyT: modifyT,
type: type,
status: status,
name: name,
desc: desc,
color: color,
dailyGoal: dailyGoal,
dailyGoalUnit: dailyGoalUnit,
dailyGoalExtra: dailyGoalExtra,
freqType: freqType,
freqCustom: freqCustom,
remindCustom: reminder,
remindQuestion: reminderQuest,
startDate: startDate,
targetDays: targetDays,
sortPosition: sortPostion,
);

@override
Map<String, dynamic> toJson() => _$WebDavSyncHabitDataToJson(this)
..[WebDavSyncHabitKey.convertType] = _convertType;

@override
String toString() => "WebDavSyncHabitData${toJson()}";
}
Loading

0 comments on commit e8f9e18

Please sign in to comment.