Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for customizing files to be excluded from the move target. #40

Merged
merged 4 commits into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 30 additions & 10 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
[![Github top language](https://img.shields.io/github/languages/top/p-x9/AppContainer)](https://github.com/p-x9/AppContainer/)

## コンセプト
通常1つのアプリに対して、1つの環境(ディレクトリ, UserDefaults、Cookie, Cache, …)が存在しています。
Debugのためや複数のアカウントを扱うために複数の環境を用意するには、複数の同一アプリをインストールする必要があります。(bundle idの異なる)
Debugにおいては、アカウントのログインとログアウトを繰り返しての確認が必要となるケースもあるかもしれません。
通常1つのアプリに対して、1つの環境(ディレクトリ, UserDefaults、Cookie, Cache, …)が存在しています。
Debugのためや複数のアカウントを扱うために複数の環境を用意するには、複数の同一アプリをインストールする必要があります。(bundle idの異なる)
Debugにおいては、アカウントのログインとログアウトを繰り返しての確認が必要となるケースもあるかもしれません。
</br>
そこで、同一アプリ内に複数の環境を作成し、簡単に切り替えることができないかと考えました。
そこで、同一アプリ内に複数の環境を作成し、簡単に切り替えることができないかと考えました。
それで作成したのが、`AppContainer`というこのライブラリです。

## デモ
Expand Down Expand Up @@ -47,7 +47,7 @@ Library/Cookies
`UserDefaults`やその上位実装である`CFPreferences`はsetされたデータを、別プロセスである`cfprefsd`というものによってキャッシングをおこなっています。
これらはsetされたデータをplistファイルに保存し永続化をおこなっていますが、上記のキャッシングにより、plist内のデータと`UserDefaults`/`CFPreferences`から取得できるデータは常に等しくなるわけではありません。(非同期で読み書きが行われる。)
これはアプリの再起動を行っても同期されるとは限りません。
よってコンテナの有効化処理を行う処理で、同期を行う処理をおこなっています。
よってコンテナの有効化処理を行う処理で、同期を行う処理をおこなっています。

### HTTPCookieStorage
HTTPCookieStorageもキャッシングされており、非同期でファイル(Library/Cookies)への書き込みが行われています。
Expand Down Expand Up @@ -114,7 +114,7 @@ try AppContainer.standard.reset()
```

### 通知(Notification)
コンテナ切り替え時に通知を受け取ることができます。
コンテナ切り替え時に通知を受け取ることができます。
厳密に、切り替え前および切り替え後に行いたい処理を追加する場合は、後述するdelegateを使用してください。

- containerWillChangeNotification
Expand Down Expand Up @@ -142,19 +142,39 @@ func appContainer(_ appContainer: AppContainer, willChangeTo toContainer: Contai
func appContainer(_ appContainer: AppContainer, didChangeTo toContainer: Container, from fromContainer: Container?) // Delegate(コンテナ切り替え後)
```

このライブラリでは複数のdelegateを設定できるようになっています。
このライブラリでは複数のdelegateを設定できるようになっています。
以下のように追加します。
```swift
AppContainer.standard.delegates.add(self) // selfがAppContainerDelegateに準拠している場合
```
弱参照で保持されており、オブジェクトが解放された場合は自動で解除されます。
弱参照で保持されており、オブジェクトが解放された場合は自動で解除されます。
もし、delegateの設定を解除したい場合は以下のように書きます。
```swift
AppContainer.standard.delegates.remove(self) // selfがAppContainerDelegateに準拠している場合
```

### コンテナ切り替え時に移動しないファイルを設定する
コンテナ切り替え時には、一部のシステムファイルを除くほぼ全てのファイルが、コンテナディレクトリへ退避そして復元されます。
これらの移動対象から除外するファイルを設定することができます。

例えば、以下はUserDefaultを全てのコンテナで共通で利用したいときの例です。
このファイルは、コンテナ切り替え時に、退避も復元もされません。
```swift
appcontainer.customExcludeFiles = [
"Library/Preferences/<Bundle Identifier>.plist"
]
```

ファイルパスのうち、最後がcustomExcludeFilesの内容に一致するものが全て移動対象から除外されます。
例えば、以下のように設定した場合、全てのディレクトリ配下の`XXX.yy`というファイルが移動対象から除外されます。
```swift
appcontainer.customExcludeFiles = [
"XXX.yy"
]
```

### AppContainerUI
AppContainerを扱うためのUIを提供しています。
AppContainerを扱うためのUIを提供しています。
SwiftUIおよびUIKitに対応しています。
#### SwiftUI
```swift
Expand All @@ -175,4 +195,4 @@ ContainerListViewController(appContainer: .standard, title: String = "Containers

// コンテナ情報を表示
ContainerInfoViewController(appContainer: .standard, container: container)
```
```
41 changes: 31 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ This is useful when you want to test multiple accounts in a staging application.
> Language Switch: [日本語](https://github.com/p-x9/AppContainer/blob/main/README.ja.md).

## Concept
Normally there is one environment (Directory, UserDefaults, Cookies, Cache, ...) for one app.
To have multiple environments for debugging or to handle multiple accounts, multiple identical apps must be installed. (with different bundle IDs).
In debugging, there may be cases where accounts are repeatedly checked by logging in and logging out.
Normally there is one environment (Directory, UserDefaults, Cookies, Cache, ...) for one app.
To have multiple environments for debugging or to handle multiple accounts, multiple identical apps must be installed. (with different bundle IDs).
In debugging, there may be cases where accounts are repeatedly checked by logging in and logging out.
</br>
Therefore, we thought it would be possible to create multiple environments within the same app and switch between them easily.
Therefore, we thought it would be possible to create multiple environments within the same app and switch between them easily.
This is why we created this library called `AppContainer`.

## Demo
Expand All @@ -35,7 +35,7 @@ This is why we created this library called `AppContainer`.
```swift
extension AppContainer {
static let group = .init(groupIdentifier: "YOUR APP GROUP IDENTIFIER")
}
}
```
### Methods
#### Create New Container
Expand Down Expand Up @@ -88,7 +88,7 @@ try AppContainer.standard.reset()
```

### Notification
You can receive notifications when switching containers.
You can receive notifications when switching containers.
If you want to add additional processing to be done strictly before and after the switch, use delegate as described below.

- containerWillChangeNotification
Expand Down Expand Up @@ -117,20 +117,41 @@ func appContainer(_ appContainer: AppContainer, willChangeTo toContainer: Contai
func appContainer(_ appContainer: AppContainer, didChangeTo toContainer: Container, from fromContainer: Container?) // Delegate (after container switch)
```

This library allows multiple delegates to be set.
This library allows multiple delegates to be set.
Add the following.

```swift
AppContainer.standard.delegates.add(self) // if self is AppContainerDelegate compliant
```
It is held in a weak reference and will be automatically released when the object is freed.
It is held in a weak reference and will be automatically released when the object is freed.
If you want to unset the delegate, write the following.
```swift
AppContainer.standard.delegates.remove(self) // if self conforms to AppContainerDelegate
```

### Set files not to be moved when switching containers
When switching containers, almost all files except for some system files are saved and restored to the container directory.
You can set files to be excluded from these moves.

For example, the following is an example of a case where you want to use UserDefault commonly in all containers.
This file will not be saved or restored when switching containers.
```swift
appcontainer.customExcludeFiles = [
"Library/Preferences/<Bundle Identifier>.plist"
]
```

All file paths that end with the contents of customExcludeFiles will be excluded from the move.
For example, the following configuration will exclude the file named `XXX.yy` under all directories.

```swift
appcontainer.customExcludeFiles = [
"XXX.yy"
]
```

### AppContainerUI
Provides UI for using AppContainer.
Provides UI for using AppContainer.
SwiftUI and UIKit are supported.
#### SwiftUI
```swift
Expand All @@ -155,4 +176,4 @@ ContainerInfoViewController(appContainer: .standard, container: container)

## Licenses

[MIT License](./LICENSE)
[MIT License](./LICENSE)
23 changes: 16 additions & 7 deletions Sources/AppContainer/AppContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ public class AppContainer {

public var delegates: WeakHashTable<any AppContainerDelegate> = .init()

/// Files to exclude from movement when switching containers.
/// For example, if you add "xxx.yy", all folders will exclude the following files named "xxx.yy".
/// It is also possible to exclude only files named "XXX.yy" under a folder named “FFF" such as "FFF/XXX.yy".
public var customExcludeFiles: [String] = []

private let fileManager = FileManager.default

private let notificationCenter = NotificationCenter.default
Expand Down Expand Up @@ -194,7 +199,7 @@ public class AppContainer {

try Container.Directories.allCases.forEach { directory in
let url = container.url(homeDirectoryUrl).appendingPathComponent(directory.name)
try self.fileManager.removeChildContents(at: url, excludes: directory.excludes)
try self.fileManager.removeChildContents(at: url, excludes: directory.excludes + customExcludeFiles)
}
}

Expand Down Expand Up @@ -384,7 +389,7 @@ extension AppContainer {
}

if groupIdentifier != nil {
let excludes = Constants.appGroupExcludeFileNames + Container.Directories.allNames
let excludes = Constants.appGroupExcludeFileNames + Container.Directories.allNames + customExcludeFiles
try fileManager.createDirectoryIfNotExisted(atPath: dst, withIntermediateDirectories: true)
try fileManager.removeChildContents(atPath: dst, excludes: excludes)
try fileManager.moveChildContents(atPath: src, toPath: dst, excludes: excludes)
Expand All @@ -394,9 +399,11 @@ extension AppContainer {
let source = src + "/" + directory.name
let destination = dst + "/" + directory.name

let excludes = directory.excludes + customExcludeFiles

try fileManager.createDirectoryIfNotExisted(atPath: destination, withIntermediateDirectories: true)
try fileManager.removeChildContents(atPath: destination, excludes: directory.excludes)
try fileManager.moveChildContents(atPath: source, toPath: destination, excludes: directory.excludes)
try fileManager.removeChildContents(atPath: destination, excludes: excludes)
try fileManager.moveChildContents(atPath: source, toPath: destination, excludes: excludes)
}
}

Expand All @@ -410,7 +417,7 @@ extension AppContainer {
}

if groupIdentifier != nil {
let excludes = Constants.appGroupExcludeFileNames + Container.Directories.allNames
let excludes = Constants.appGroupExcludeFileNames + Container.Directories.allNames + customExcludeFiles
try fileManager.createDirectoryIfNotExisted(atPath: dst, withIntermediateDirectories: true)
try fileManager.removeChildContents(atPath: dst, excludes: excludes)
try fileManager.copyChildContents(atPath: src, toPath: dst, excludes: excludes)
Expand All @@ -420,9 +427,11 @@ extension AppContainer {
let source = src + "/" + directory.name
let destination = dst + "/" + directory.name

let excludes = directory.excludes + customExcludeFiles

try fileManager.createDirectoryIfNotExisted(atPath: destination, withIntermediateDirectories: true)
try fileManager.removeChildContents(atPath: destination, excludes: directory.excludes)
try fileManager.copyChildContents(atPath: source, toPath: destination, excludes: directory.excludes)
try fileManager.removeChildContents(atPath: destination, excludes: excludes)
try fileManager.copyChildContents(atPath: source, toPath: destination, excludes: excludes)
}
}

Expand Down
Loading