all: minimal set of changes for iOS app (#9619)

### Purpose

This PR contains the set of changes needed to make Syncthing work on iOS
for [my iOS app for
Syncthing](https://github.com/pixelspark/sushitrain).

Most changes originate from [the Mobius Sync
fork](http://github.com/MobiusSync/syncthing/tree/ios). I have removed
the changes from their fork that are not strictly needed for my app
(i.e. their changes to the GUI and command line utilities, for instance)
and squashed it all in a single commit.

In summary, the changes are:

* Resolve non-absolute paths to the 'Documents' folder (basically the
only one an app can/should write user data to by default on iOS)
* Tweaking of build flags/conditions for iOS (i.e. determine which
basicfs_watch, ignoreresult variant to build for iOS)
* Disable upgrade mechanism on iOS
* Make `RequestGlobal` and `PullerProgress` public symbols
* Expose syncthing.app's Model instance (app.M)
* Add no-op stub for SetLowPriority on iOS

I would very much appreciate these changes to be (eventually) merged to
mainline syncthing, as this would allow my iOS app to track the mainline
source code directly and removes the need (for me at least) for
maintaining a separate fork. Perhaps the Mobius folks can also benefit
from this (although as noted this branch does not contain their changes
to e.g. the GUI).

### Testing

This branch has been tested with the iOS app and appears to work fine.
The full set of MobiusSync changes has been used before with success.

### Screenshots

n/a

### Documentation

There should be no visible changes for users due to this set of changes.

---------

Co-authored-by: Simon Pickup <simon@pickupinfinity.com>
This commit is contained in:
Tommy van der Vorst
2024-07-31 07:31:14 +02:00
committed by GitHub
co-authored by Simon Pickup
parent e738af7c56
commit de0b4270df
22 changed files with 156 additions and 30 deletions
+1 -1
View File
@@ -1579,7 +1579,7 @@ loop:
activity.using(selected)
var buf []byte
blockNo := int(state.block.Offset / int64(state.file.BlockSize()))
buf, lastError = f.model.requestGlobal(f.ctx, selected.ID, f.folderID, state.file.Name, blockNo, state.block.Offset, int(state.block.Size), state.block.Hash, state.block.WeakHash, selected.FromTemporary)
buf, lastError = f.model.RequestGlobal(f.ctx, selected.ID, f.folderID, state.file.Name, blockNo, state.block.Offset, int(state.block.Size), state.block.Hash, state.block.WeakHash, selected.FromTemporary)
activity.done(selected)
if lastError != nil {
l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, selected.ID.Short(), "returned error:", lastError)
+1 -1
View File
@@ -261,7 +261,7 @@ func (c *folderSummaryService) processUpdate(ev events.Event) {
return
case events.DownloadProgress:
data := ev.Data.(map[string]map[string]*pullerProgress)
data := ev.Data.(map[string]map[string]*PullerProgress)
c.foldersMut.Lock()
for folder := range data {
c.folders[folder] = struct{}{}
+102
View File
@@ -435,6 +435,28 @@ type Model struct {
result1 protocol.RequestResponse
result2 error
}
RequestGlobalStub func(context.Context, protocol.DeviceID, string, string, int, int64, int, []byte, uint32, bool) ([]byte, error)
requestGlobalMutex sync.RWMutex
requestGlobalArgsForCall []struct {
arg1 context.Context
arg2 protocol.DeviceID
arg3 string
arg4 string
arg5 int
arg6 int64
arg7 int
arg8 []byte
arg9 uint32
arg10 bool
}
requestGlobalReturns struct {
result1 []byte
result2 error
}
requestGlobalReturnsOnCall map[int]struct {
result1 []byte
result2 error
}
ResetFolderStub func(string) error
resetFolderMutex sync.RWMutex
resetFolderArgsForCall []struct {
@@ -2558,6 +2580,84 @@ func (fake *Model) RequestReturnsOnCall(i int, result1 protocol.RequestResponse,
}{result1, result2}
}
func (fake *Model) RequestGlobal(arg1 context.Context, arg2 protocol.DeviceID, arg3 string, arg4 string, arg5 int, arg6 int64, arg7 int, arg8 []byte, arg9 uint32, arg10 bool) ([]byte, error) {
var arg8Copy []byte
if arg8 != nil {
arg8Copy = make([]byte, len(arg8))
copy(arg8Copy, arg8)
}
fake.requestGlobalMutex.Lock()
ret, specificReturn := fake.requestGlobalReturnsOnCall[len(fake.requestGlobalArgsForCall)]
fake.requestGlobalArgsForCall = append(fake.requestGlobalArgsForCall, struct {
arg1 context.Context
arg2 protocol.DeviceID
arg3 string
arg4 string
arg5 int
arg6 int64
arg7 int
arg8 []byte
arg9 uint32
arg10 bool
}{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8Copy, arg9, arg10})
stub := fake.RequestGlobalStub
fakeReturns := fake.requestGlobalReturns
fake.recordInvocation("RequestGlobal", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8Copy, arg9, arg10})
fake.requestGlobalMutex.Unlock()
if stub != nil {
return stub(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10)
}
if specificReturn {
return ret.result1, ret.result2
}
return fakeReturns.result1, fakeReturns.result2
}
func (fake *Model) RequestGlobalCallCount() int {
fake.requestGlobalMutex.RLock()
defer fake.requestGlobalMutex.RUnlock()
return len(fake.requestGlobalArgsForCall)
}
func (fake *Model) RequestGlobalCalls(stub func(context.Context, protocol.DeviceID, string, string, int, int64, int, []byte, uint32, bool) ([]byte, error)) {
fake.requestGlobalMutex.Lock()
defer fake.requestGlobalMutex.Unlock()
fake.RequestGlobalStub = stub
}
func (fake *Model) RequestGlobalArgsForCall(i int) (context.Context, protocol.DeviceID, string, string, int, int64, int, []byte, uint32, bool) {
fake.requestGlobalMutex.RLock()
defer fake.requestGlobalMutex.RUnlock()
argsForCall := fake.requestGlobalArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6, argsForCall.arg7, argsForCall.arg8, argsForCall.arg9, argsForCall.arg10
}
func (fake *Model) RequestGlobalReturns(result1 []byte, result2 error) {
fake.requestGlobalMutex.Lock()
defer fake.requestGlobalMutex.Unlock()
fake.RequestGlobalStub = nil
fake.requestGlobalReturns = struct {
result1 []byte
result2 error
}{result1, result2}
}
func (fake *Model) RequestGlobalReturnsOnCall(i int, result1 []byte, result2 error) {
fake.requestGlobalMutex.Lock()
defer fake.requestGlobalMutex.Unlock()
fake.RequestGlobalStub = nil
if fake.requestGlobalReturnsOnCall == nil {
fake.requestGlobalReturnsOnCall = make(map[int]struct {
result1 []byte
result2 error
})
}
fake.requestGlobalReturnsOnCall[i] = struct {
result1 []byte
result2 error
}{result1, result2}
}
func (fake *Model) ResetFolder(arg1 string) error {
fake.resetFolderMutex.Lock()
ret, specificReturn := fake.resetFolderReturnsOnCall[len(fake.resetFolderArgsForCall)]
@@ -3258,6 +3358,8 @@ func (fake *Model) Invocations() map[string][][]interface{} {
defer fake.remoteNeedFolderFilesMutex.RUnlock()
fake.requestMutex.RLock()
defer fake.requestMutex.RUnlock()
fake.requestGlobalMutex.RLock()
defer fake.requestGlobalMutex.RUnlock()
fake.resetFolderMutex.RLock()
defer fake.resetFolderMutex.RUnlock()
fake.restoreFolderVersionsMutex.RLock()
+5 -3
View File
@@ -117,6 +117,8 @@ type Model interface {
DismissPendingFolder(device protocol.DeviceID, folder string) error
GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly bool) ([]*TreeEntry, error)
RequestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error)
}
type model struct {
@@ -375,7 +377,7 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio
// it'll show up as errored later.
if err := cfg.CreateRoot(); err != nil {
l.Warnln("Failed to create folder root directory", err)
l.Warnln("Failed to create folder root directory:", err)
} else if err = cfg.CreateMarker(); err != nil {
l.Warnln("Failed to create folder marker:", err)
}
@@ -2460,7 +2462,7 @@ func (m *model) deviceDidCloseRLocked(deviceID protocol.DeviceID, duration time.
}
}
func (m *model) requestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
func (m *model) RequestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
conn, connOK := m.requestConnectionForDevice(deviceID)
if !connOK {
return nil, fmt.Errorf("requestGlobal: no connection to device: %s", deviceID.Short())
@@ -2566,7 +2568,7 @@ func (m *model) numHashers(folder string) int {
return folderCfg.Hashers
}
if build.IsWindows || build.IsDarwin || build.IsAndroid {
if build.IsWindows || build.IsDarwin || build.IsIOS || build.IsAndroid {
// Interactive operating systems; don't load the system too heavily by
// default.
return 1
+1 -1
View File
@@ -222,7 +222,7 @@ func BenchmarkRequestOut(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
data, err := m.requestGlobal(context.Background(), device1, "default", files[i%n].Name, 0, 0, 32, nil, 0, false)
data, err := m.RequestGlobal(context.Background(), device1, "default", files[i%n].Name, 0, 0, 32, nil, 0, false)
if err != nil {
b.Error(err)
}
+2 -2
View File
@@ -118,12 +118,12 @@ func (t *ProgressEmitter) Serve(ctx context.Context) error {
}
func (t *ProgressEmitter) sendDownloadProgressEventLocked() {
output := make(map[string]map[string]*pullerProgress)
output := make(map[string]map[string]*PullerProgress)
for folder, pullers := range t.registry {
if len(pullers) == 0 {
continue
}
output[folder] = make(map[string]*pullerProgress)
output[folder] = make(map[string]*PullerProgress)
for name, puller := range pullers {
output[folder][name] = puller.Progress()
}
+1 -1
View File
@@ -39,7 +39,7 @@ func expectEvent(w events.Subscription, t *testing.T, size int) {
if event.Type != events.DownloadProgress {
t.Fatal("Unexpected event:", event, "at", caller(1))
}
data := event.Data.(map[string]map[string]*pullerProgress)
data := event.Data.(map[string]map[string]*PullerProgress)
if len(data) != size {
t.Fatal("Unexpected event data size:", data, "at", caller(1))
}
+3 -3
View File
@@ -75,7 +75,7 @@ func newSharedPullerState(file protocol.FileInfo, fs fs.Filesystem, folderID, te
}
// A momentary state representing the progress of the puller
type pullerProgress struct {
type PullerProgress struct {
Total int `json:"total"`
Reused int `json:"reused"`
CopiedFromOrigin int `json:"copiedFromOrigin"`
@@ -405,13 +405,13 @@ func encryptionTrailerSize(file protocol.FileInfo) int64 {
}
// Progress returns the momentarily progress for the puller
func (s *sharedPullerState) Progress() *pullerProgress {
func (s *sharedPullerState) Progress() *PullerProgress {
s.mut.RLock()
defer s.mut.RUnlock()
total := s.reused + s.copyTotal + s.pullTotal
done := total - s.copyNeeded - s.pullNeeded
file := len(s.file.Blocks)
return &pullerProgress{
return &PullerProgress{
Total: total,
Reused: s.reused,
CopiedFromOrigin: s.copyOrigin,