|
| 1 | +#Output Streaming Implementation Guide |
| 2 | + |
| 3 | +##Problem Statement |
| 4 | +Currently, RsyncUI buffers the**entire** rsync output in memory before processing it. For large sync operations with thousands of files, this can: |
| 5 | +- Consume excessive memory |
| 6 | +- Delay feedback to the user |
| 7 | +- Make the app unresponsive during large transfers |
| 8 | + |
| 9 | +##Solution: Stream Processing |
| 10 | + |
| 11 | +Process rsync output**line-by-line** as it arrives, instead of waiting for the entire output. |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +##Implementation Options |
| 16 | + |
| 17 | +###Option 1: Use Existing`printLine` Closure ✅ RECOMMENDED |
| 18 | + |
| 19 | +Your`RsyncProcess` package already supports streaming via the`printLine` parameter! This is visible in: |
| 20 | +-`CreateHandlers.swift` line 33:`printLine: RsyncOutputCapture.shared.makePrintLinesClosure()` |
| 21 | + |
| 22 | +**Advantages:** |
| 23 | +- No need to modify RsyncProcess package |
| 24 | +- Already working for`RsyncRealtimeView` |
| 25 | +- Clean separation of concerns |
| 26 | + |
| 27 | +**How to Implement:** |
| 28 | + |
| 29 | +1.**Use`StreamingOutputHandler.swift`** (already created) |
| 30 | +- Manages a rolling buffer (keeps last N lines) |
| 31 | +- Provides callbacks for line-by-line and batch processing |
| 32 | +- Prevents memory overflow |
| 33 | + |
| 34 | +2.**Use`CreateStreamingHandlers.swift`** (already created) |
| 35 | +- Wrapper around`CreateHandlers` |
| 36 | +- Injects streaming handler into the flow |
| 37 | +- Maintains compatibility with existing code |
| 38 | + |
| 39 | +3.**Update your classes** (example:`EstimateWithStreaming.swift`) |
| 40 | +```swift |
| 41 | +// Before: Waits for all output |
| 42 | +let handlers=CreateHandlers().createHandlers(...) |
| 43 | + |
| 44 | +// After: Streams output line-by-line |
| 45 | +let streamingHandler=StreamingOutputHandler( |
| 46 | +maxBufferSize:100, |
| 47 | +onLineReceived: { linein |
| 48 | +// Process each line immediately |
| 49 | +self.handleLine(line) |
| 50 | + } |
| 51 | + ) |
| 52 | + |
| 53 | +let handlers=CreateStreamingHandlers().createHandlers( |
| 54 | +fileHandler: fileHandler, |
| 55 | +processTermination: processTermination, |
| 56 | +streamingHandler: streamingHandler |
| 57 | + ) |
| 58 | +``` |
| 59 | + |
| 60 | +--- |
| 61 | + |
| 62 | +###Option 2: Modify RsyncProcess Package (If Needed) |
| 63 | + |
| 64 | +If`printLine` isn't working or you need more control, implement streaming at the`Process` level using`Pipe.readabilityHandler`. |
| 65 | + |
| 66 | +See`ProcessStreamingExample.swift` for a complete implementation showing: |
| 67 | +- How to use`fileHandle.readabilityHandler` |
| 68 | +- Line-by-line processing with partial line handling |
| 69 | +- Memory-efficient streaming |
| 70 | + |
| 71 | +**Key Code:** |
| 72 | +```swift |
| 73 | +outputPipe.fileHandleForReading.readabilityHandler= { fileHandlein |
| 74 | +let data= fileHandle.availableData// Non-blocking read |
| 75 | +// Process data immediately as it arrives |
| 76 | +onLineReceived(line) |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +--- |
| 81 | + |
| 82 | +##Migration Strategy |
| 83 | + |
| 84 | +###Phase 1: Test with Quick Task ✅ Low Risk |
| 85 | +1. Modify`extensionQuickTaskView.swift` |
| 86 | +2. Use`CreateStreamingHandlers` instead of`CreateHandlers` |
| 87 | +3. Test with small sync operations |
| 88 | + |
| 89 | +###Phase 2: Update Estimate |
| 90 | +1. Modify`Estimate.swift` using`EstimateWithStreaming.swift` as reference |
| 91 | +2. Keep rolling buffer of last 100 lines |
| 92 | +3. Only send summary (last 20 lines) to UI |
| 93 | + |
| 94 | +###Phase 3: Update Execute |
| 95 | +1. Modify`Execute.swift` |
| 96 | +2. Stream output to progress indicator |
| 97 | +3. Use streaming for log file writing |
| 98 | + |
| 99 | +###Phase 4: Update Verify Operations |
| 100 | +1.`VerifyTasks.swift` |
| 101 | +2.`ExecutePushPullView.swift` |
| 102 | +3.`PushPullView.swift` |
| 103 | + |
| 104 | +--- |
| 105 | + |
| 106 | +##Benefits |
| 107 | + |
| 108 | +###Memory Usage |
| 109 | +**Before:** |
| 110 | +- 10,000 file sync = 10,000 lines buffered =~500 KB per operation |
| 111 | +- Multiple simultaneous operations = potential memory issues |
| 112 | + |
| 113 | +**After:** |
| 114 | +- Rolling buffer of 100 lines =~5 KB per operation |
| 115 | +- 99% memory reduction |
| 116 | + |
| 117 | +###User Experience |
| 118 | +**Before:** |
| 119 | +- No feedback until process completes |
| 120 | +- UI appears frozen on large transfers |
| 121 | + |
| 122 | +**After:** |
| 123 | +- Real-time line-by-line updates |
| 124 | +- Responsive UI during transfers |
| 125 | +- Better progress indication |
| 126 | + |
| 127 | +###Performance |
| 128 | +**Before:** |
| 129 | +- Process finishes → Parse 10,000 lines → Create UI data → Display |
| 130 | +- User waits for all steps |
| 131 | + |
| 132 | +**After:** |
| 133 | +- Line arrives → Display immediately |
| 134 | +- Continuous feedback |
| 135 | + |
| 136 | +--- |
| 137 | + |
| 138 | +##Files Created |
| 139 | + |
| 140 | +1.**`StreamingOutputHandler.swift`** |
| 141 | +- Core streaming logic |
| 142 | +- Rolling buffer management |
| 143 | +- Callbacks for line/batch processing |
| 144 | + |
| 145 | +2.**`CreateStreamingHandlers.swift`** |
| 146 | +- Drop-in replacement for`CreateHandlers` |
| 147 | +- Integrates streaming handler |
| 148 | +- Maintains API compatibility |
| 149 | + |
| 150 | +3.**`EstimateWithStreaming.swift`** |
| 151 | +- Example implementation for Estimate |
| 152 | +- Shows how to use streaming in practice |
| 153 | + |
| 154 | +4.**`ProcessStreamingExample.swift`** |
| 155 | +- Low-level reference implementation |
| 156 | +- Use if modifying RsyncProcess package |
| 157 | + |
| 158 | +--- |
| 159 | + |
| 160 | +##Testing |
| 161 | + |
| 162 | +1.**Small sync** (< 100 files) |
| 163 | +- Verify no regression |
| 164 | +- Check output completeness |
| 165 | + |
| 166 | +2.**Large sync** (> 1,000 files) |
| 167 | +- Monitor memory usage (should stay low) |
| 168 | +- Verify UI responsiveness |
| 169 | +- Check that summary data is correct |
| 170 | + |
| 171 | +3.**Error cases** |
| 172 | +- Rsync errors are caught immediately |
| 173 | +- Partial output handled correctly |
| 174 | + |
| 175 | +4.**Real-time view** |
| 176 | +-`RsyncRealtimeView` continues to work |
| 177 | +- No duplicate output |
| 178 | + |
| 179 | +--- |
| 180 | + |
| 181 | +##Configuration |
| 182 | + |
| 183 | +In`StreamingOutputHandler`: |
| 184 | +```swift |
| 185 | +maxBufferSize:Int=100// Adjust based on needs |
| 186 | +``` |
| 187 | + |
| 188 | +**Recommendations:** |
| 189 | +-**Estimate operations**: 100 lines (only need summary) |
| 190 | +-**Execute operations**: 200 lines (keep more context) |
| 191 | +-**Restore operations**: 500 lines (user wants full list) |
| 192 | +-**Quick tasks**: 100 lines (small operations) |
| 193 | + |
| 194 | +--- |
| 195 | + |
| 196 | +##Rollback Plan |
| 197 | + |
| 198 | +If streaming causes issues: |
| 199 | +1. Keep`CreateHandlers` unchanged |
| 200 | +2. Use`CreateStreamingHandlers` only where needed |
| 201 | +3. Easy to revert individual classes |
| 202 | + |
| 203 | +--- |
| 204 | + |
| 205 | +##Next Steps |
| 206 | + |
| 207 | +1.**Test`printLine` behavior** |
| 208 | +- Verify it's called line-by-line |
| 209 | +- Check timing (immediate vs batched) |
| 210 | + |
| 211 | +2.**Start with Quick Task** |
| 212 | +- Lowest risk area |
| 213 | +- Quick to test and verify |
| 214 | + |
| 215 | +3.**Gradually migrate** |
| 216 | +- One class at a time |
| 217 | +- Monitor memory and performance |
| 218 | + |
| 219 | +4.**Remove buffering in PrepareOutputFromRsync** |
| 220 | +- Currently keeps entire output to extract last 20 lines |
| 221 | +- With streaming, can extract during process |
| 222 | +- Further memory savings |
| 223 | + |
| 224 | +--- |
| 225 | + |
| 226 | +##Code Quality Notes |
| 227 | + |
| 228 | +✅**Follows existing patterns** |
| 229 | +- Uses`@MainActor` appropriately |
| 230 | +- Proper logging with`Logger.process` |
| 231 | +- Error handling via`SharedReference.shared.errorobject` |
| 232 | + |
| 233 | +✅**Maintains compatibility** |
| 234 | +- Doesn't break existing code |
| 235 | +- Optional adoption |
| 236 | +- Can run both approaches side-by-side |
| 237 | + |
| 238 | +✅**Memory safe** |
| 239 | +- Rolling buffer prevents unbounded growth |
| 240 | +- Configurable limits |
| 241 | +- Clear ownership and lifecycle |
| 242 | + |
| 243 | +--- |
| 244 | + |
| 245 | +##References |
| 246 | + |
| 247 | +- Original recommendation:`AIDocuments/CODE_QUALITY_ANALYSIS_COMPREHENSIVE_DEC16.md` line 705 |
| 248 | +- Existing streaming UI:`RsyncUI/Views/OutputViews/RsyncRealtimeView.swift` |
| 249 | +- Current handler:`RsyncUI/Model/Execution/CreateHandlers/CreateHandlers.swift` |
| 250 | +- Output capture: Check RsyncProcess package's`PrintLines.swift` |