├── .gitignore ├── Couchbase Lite Viewer.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ ├── Couchbase Lite Viewer.xccheckout │ └── TouchDB Viewer.xccheckout ├── Couchbase Lite Viewer ├── DBDoc.icns └── Images.xcassets │ ├── AppIcon.appiconset │ ├── AppIcon128x128.png │ └── Contents.json │ ├── Contents.json │ ├── database.imageset │ ├── Contents.json │ └── database.png │ ├── database2.imageset │ ├── Contents.json │ └── database2.png │ └── ios_app.imageset │ ├── Contents.json │ └── ios_app.png ├── Frameworks └── README.md ├── README.md └── Source ├── AppDelegate.h ├── AppDelegate.m ├── AppList.h ├── AppList.m ├── AppListController.h ├── AppListController.m ├── AppListController.xib ├── Couchbase Lite Viewer-Info.plist ├── Couchbase Lite Viewer-Prefix.pch ├── DBDocument.h ├── DBDocument.m ├── DBWindowController.h ├── DBWindowController.m ├── DBWindowController.xib ├── DocEditor.h ├── DocEditor.m ├── DocHistory.h ├── DocHistory.m ├── JSONFormatter.h ├── JSONFormatter.m ├── JSONItem.h ├── JSONItem.m ├── JSONKeyFormatter.h ├── JSONKeyFormatter.m ├── QueryResultController.h ├── QueryResultController.m ├── RevTreeController.h ├── RevTreeController.m ├── URLFormatter.h ├── URLFormatter.m ├── en.lproj ├── Credits.rtf ├── InfoPlist.strings └── MainMenu.xib └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pbxuser 3 | *.perspectivev3 4 | *.mode1v3 5 | *.framework 6 | *.app 7 | *.zip 8 | xcuserdata/ 9 | build/ 10 | DerivedData/ 11 | -------------------------------------------------------------------------------- /Couchbase Lite Viewer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2733A75D1BD70E5600F0FF07 /* JSONItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 2733A75C1BD70E5600F0FF07 /* JSONItem.m */; }; 11 | 274789FF15F97520006E842D /* URLFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 274789FE15F97520006E842D /* URLFormatter.m */; }; 12 | 27538FA517F28B31004C3BFD /* CouchbaseLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27538FA417F28B31004C3BFD /* CouchbaseLite.framework */; }; 13 | 27538FA617F29303004C3BFD /* CouchbaseLite.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 27538FA417F28B31004C3BFD /* CouchbaseLite.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 14 | 27538FA917F34C09004C3BFD /* AppList.m in Sources */ = {isa = PBXBuildFile; fileRef = 27538FA817F34C09004C3BFD /* AppList.m */; }; 15 | 27538FAF17F3607C004C3BFD /* AppListController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27538FAD17F3607C004C3BFD /* AppListController.m */; }; 16 | 27538FB017F3607C004C3BFD /* AppListController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 27538FAE17F3607C004C3BFD /* AppListController.xib */; }; 17 | 2765CF7815544FAA0087D810 /* DocEditor.m in Sources */ = {isa = PBXBuildFile; fileRef = 2765CF7715544FAA0087D810 /* DocEditor.m */; }; 18 | 276AA6C21BD846CD00973893 /* JSONKeyFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 276AA6C11BD846CD00973893 /* JSONKeyFormatter.m */; }; 19 | 278B0C6F152A64A900577747 /* DBDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 278B0C6D152A64A900577747 /* DBDocument.m */; }; 20 | 278C85B81554810D0016C7A4 /* JSONFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 278C85B71554810D0016C7A4 /* JSONFormatter.m */; }; 21 | 27A8A777152A008200363704 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27A8A776152A008200363704 /* Cocoa.framework */; }; 22 | 27A8A781152A008200363704 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 27A8A77F152A008200363704 /* InfoPlist.strings */; }; 23 | 27A8A783152A008200363704 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A8A782152A008200363704 /* main.m */; }; 24 | 27A8A787152A008200363704 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 27A8A785152A008200363704 /* Credits.rtf */; }; 25 | 27A8A78A152A008200363704 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A8A789152A008200363704 /* AppDelegate.m */; }; 26 | 27A8A78D152A008200363704 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 27A8A78B152A008200363704 /* MainMenu.xib */; }; 27 | 27A8A79A152A01FF00363704 /* DBWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A8A798152A01FF00363704 /* DBWindowController.m */; }; 28 | 27A8A79B152A01FF00363704 /* DBWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 27A8A799152A01FF00363704 /* DBWindowController.xib */; }; 29 | 27DC377C1BDE8BAB00734E82 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 27DC377B1BDE8BAB00734E82 /* Images.xcassets */; }; 30 | 27DC377E1BDE8EC100734E82 /* DBDoc.icns in Resources */ = {isa = PBXBuildFile; fileRef = 27DC377D1BDE8EC100734E82 /* DBDoc.icns */; }; 31 | 27F3A62D15EE70B300263663 /* DocHistory.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F3A62C15EE70B300263663 /* DocHistory.m */; }; 32 | 27F3A63015EE98C100263663 /* QueryResultController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F3A62F15EE98C100263663 /* QueryResultController.m */; }; 33 | 27F3A63315EEA06900263663 /* RevTreeController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F3A63215EEA06900263663 /* RevTreeController.m */; }; 34 | /* End PBXBuildFile section */ 35 | 36 | /* Begin PBXCopyFilesBuildPhase section */ 37 | 273A438E153C935F00AEC9D7 /* Copy Frameworks */ = { 38 | isa = PBXCopyFilesBuildPhase; 39 | buildActionMask = 2147483647; 40 | dstPath = ""; 41 | dstSubfolderSpec = 10; 42 | files = ( 43 | 27538FA617F29303004C3BFD /* CouchbaseLite.framework in Copy Frameworks */, 44 | ); 45 | name = "Copy Frameworks"; 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXCopyFilesBuildPhase section */ 49 | 50 | /* Begin PBXFileReference section */ 51 | 1E1B56D41C7396690096DF2B /* CBLRegisterJSViewCompiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLRegisterJSViewCompiler.h; sourceTree = ""; }; 52 | 2733A75B1BD70E5600F0FF07 /* JSONItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONItem.h; sourceTree = ""; }; 53 | 2733A75C1BD70E5600F0FF07 /* JSONItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONItem.m; sourceTree = ""; }; 54 | 274789FD15F97520006E842D /* URLFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = URLFormatter.h; sourceTree = ""; }; 55 | 274789FE15F97520006E842D /* URLFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = URLFormatter.m; sourceTree = ""; }; 56 | 27538FA417F28B31004C3BFD /* CouchbaseLite.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CouchbaseLite.framework; path = Frameworks/CouchbaseLite.framework; sourceTree = ""; }; 57 | 27538FA717F34C09004C3BFD /* AppList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppList.h; sourceTree = ""; }; 58 | 27538FA817F34C09004C3BFD /* AppList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppList.m; sourceTree = ""; }; 59 | 27538FAC17F3607C004C3BFD /* AppListController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppListController.h; sourceTree = ""; }; 60 | 27538FAD17F3607C004C3BFD /* AppListController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppListController.m; sourceTree = ""; }; 61 | 27538FAE17F3607C004C3BFD /* AppListController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AppListController.xib; sourceTree = ""; }; 62 | 2765CF7615544FAA0087D810 /* DocEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DocEditor.h; sourceTree = ""; }; 63 | 2765CF7715544FAA0087D810 /* DocEditor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DocEditor.m; sourceTree = ""; }; 64 | 276AA6C01BD846CD00973893 /* JSONKeyFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONKeyFormatter.h; sourceTree = ""; }; 65 | 276AA6C11BD846CD00973893 /* JSONKeyFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONKeyFormatter.m; sourceTree = ""; }; 66 | 278B0C6C152A64A900577747 /* DBDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBDocument.h; sourceTree = ""; }; 67 | 278B0C6D152A64A900577747 /* DBDocument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBDocument.m; sourceTree = ""; }; 68 | 278C85B61554810D0016C7A4 /* JSONFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONFormatter.h; sourceTree = ""; }; 69 | 278C85B71554810D0016C7A4 /* JSONFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONFormatter.m; sourceTree = ""; }; 70 | 27A8A772152A008200363704 /* Couchbase Lite Viewer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Couchbase Lite Viewer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 71 | 27A8A776152A008200363704 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 72 | 27A8A779152A008200363704 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 73 | 27A8A77A152A008200363704 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 74 | 27A8A77B152A008200363704 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 75 | 27A8A77E152A008200363704 /* Couchbase Lite Viewer-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Couchbase Lite Viewer-Info.plist"; sourceTree = ""; }; 76 | 27A8A780152A008200363704 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 77 | 27A8A782152A008200363704 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 78 | 27A8A784152A008200363704 /* Couchbase Lite Viewer-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Couchbase Lite Viewer-Prefix.pch"; sourceTree = ""; }; 79 | 27A8A786152A008200363704 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; 80 | 27A8A788152A008200363704 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 81 | 27A8A789152A008200363704 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 82 | 27A8A78C152A008200363704 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; 83 | 27A8A797152A01FF00363704 /* DBWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBWindowController.h; sourceTree = ""; }; 84 | 27A8A798152A01FF00363704 /* DBWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBWindowController.m; sourceTree = ""; }; 85 | 27A8A799152A01FF00363704 /* DBWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DBWindowController.xib; sourceTree = ""; }; 86 | 27DC377B1BDE8BAB00734E82 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = "Couchbase Lite Viewer/Images.xcassets"; sourceTree = SOURCE_ROOT; }; 87 | 27DC377D1BDE8EC100734E82 /* DBDoc.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = DBDoc.icns; path = "Couchbase Lite Viewer/DBDoc.icns"; sourceTree = SOURCE_ROOT; }; 88 | 27F3A62B15EE70B300263663 /* DocHistory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DocHistory.h; sourceTree = ""; }; 89 | 27F3A62C15EE70B300263663 /* DocHistory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DocHistory.m; sourceTree = ""; }; 90 | 27F3A62E15EE98C000263663 /* QueryResultController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QueryResultController.h; sourceTree = ""; }; 91 | 27F3A62F15EE98C100263663 /* QueryResultController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QueryResultController.m; sourceTree = ""; }; 92 | 27F3A63115EEA06900263663 /* RevTreeController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RevTreeController.h; sourceTree = ""; }; 93 | 27F3A63215EEA06900263663 /* RevTreeController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RevTreeController.m; sourceTree = ""; }; 94 | 27F87AFD1555EC5000F0A416 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = ""; }; 95 | /* End PBXFileReference section */ 96 | 97 | /* Begin PBXFrameworksBuildPhase section */ 98 | 27A8A76F152A008200363704 /* Frameworks */ = { 99 | isa = PBXFrameworksBuildPhase; 100 | buildActionMask = 2147483647; 101 | files = ( 102 | 27538FA517F28B31004C3BFD /* CouchbaseLite.framework in Frameworks */, 103 | 27A8A777152A008200363704 /* Cocoa.framework in Frameworks */, 104 | ); 105 | runOnlyForDeploymentPostprocessing = 0; 106 | }; 107 | /* End PBXFrameworksBuildPhase section */ 108 | 109 | /* Begin PBXGroup section */ 110 | 27A8A767152A008100363704 = { 111 | isa = PBXGroup; 112 | children = ( 113 | 27F87AFD1555EC5000F0A416 /* README.md */, 114 | 27A8A77C152A008200363704 /* Couchbase Lite Viewer */, 115 | 27A8A775152A008200363704 /* Frameworks */, 116 | 27A8A773152A008200363704 /* Products */, 117 | ); 118 | sourceTree = ""; 119 | }; 120 | 27A8A773152A008200363704 /* Products */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 27A8A772152A008200363704 /* Couchbase Lite Viewer.app */, 124 | ); 125 | name = Products; 126 | sourceTree = ""; 127 | }; 128 | 27A8A775152A008200363704 /* Frameworks */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 27538FA417F28B31004C3BFD /* CouchbaseLite.framework */, 132 | 27A8A776152A008200363704 /* Cocoa.framework */, 133 | 27A8A778152A008200363704 /* Other Frameworks */, 134 | ); 135 | name = Frameworks; 136 | sourceTree = ""; 137 | }; 138 | 27A8A778152A008200363704 /* Other Frameworks */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 27A8A779152A008200363704 /* AppKit.framework */, 142 | 27A8A77A152A008200363704 /* CoreData.framework */, 143 | 27A8A77B152A008200363704 /* Foundation.framework */, 144 | ); 145 | name = "Other Frameworks"; 146 | sourceTree = ""; 147 | }; 148 | 27A8A77C152A008200363704 /* Couchbase Lite Viewer */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | 27A8A788152A008200363704 /* AppDelegate.h */, 152 | 27A8A789152A008200363704 /* AppDelegate.m */, 153 | 27A8A78B152A008200363704 /* MainMenu.xib */, 154 | 27E11A631BD571DC00D8DB7D /* Database Window */, 155 | 27E11A611BD571B600D8DB7D /* App Browser */, 156 | 27E11A621BD571CB00D8DB7D /* Utilities */, 157 | 27A8A77D152A008200363704 /* Supporting Files */, 158 | ); 159 | name = "Couchbase Lite Viewer"; 160 | path = Source; 161 | sourceTree = ""; 162 | }; 163 | 27A8A77D152A008200363704 /* Supporting Files */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | 27DC377B1BDE8BAB00734E82 /* Images.xcassets */, 167 | 1E1B56D41C7396690096DF2B /* CBLRegisterJSViewCompiler.h */, 168 | 27DC377D1BDE8EC100734E82 /* DBDoc.icns */, 169 | 27A8A77E152A008200363704 /* Couchbase Lite Viewer-Info.plist */, 170 | 27A8A77F152A008200363704 /* InfoPlist.strings */, 171 | 27A8A782152A008200363704 /* main.m */, 172 | 27A8A784152A008200363704 /* Couchbase Lite Viewer-Prefix.pch */, 173 | 27A8A785152A008200363704 /* Credits.rtf */, 174 | ); 175 | name = "Supporting Files"; 176 | sourceTree = ""; 177 | }; 178 | 27E11A611BD571B600D8DB7D /* App Browser */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 27538FA717F34C09004C3BFD /* AppList.h */, 182 | 27538FA817F34C09004C3BFD /* AppList.m */, 183 | 27538FAC17F3607C004C3BFD /* AppListController.h */, 184 | 27538FAD17F3607C004C3BFD /* AppListController.m */, 185 | 27538FAE17F3607C004C3BFD /* AppListController.xib */, 186 | ); 187 | name = "App Browser"; 188 | sourceTree = ""; 189 | }; 190 | 27E11A621BD571CB00D8DB7D /* Utilities */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | 27F3A62B15EE70B300263663 /* DocHistory.h */, 194 | 27F3A62C15EE70B300263663 /* DocHistory.m */, 195 | 278C85B61554810D0016C7A4 /* JSONFormatter.h */, 196 | 278C85B71554810D0016C7A4 /* JSONFormatter.m */, 197 | 276AA6C01BD846CD00973893 /* JSONKeyFormatter.h */, 198 | 276AA6C11BD846CD00973893 /* JSONKeyFormatter.m */, 199 | 2733A75B1BD70E5600F0FF07 /* JSONItem.h */, 200 | 2733A75C1BD70E5600F0FF07 /* JSONItem.m */, 201 | 274789FD15F97520006E842D /* URLFormatter.h */, 202 | 274789FE15F97520006E842D /* URLFormatter.m */, 203 | ); 204 | name = Utilities; 205 | sourceTree = ""; 206 | }; 207 | 27E11A631BD571DC00D8DB7D /* Database Window */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | 278B0C6C152A64A900577747 /* DBDocument.h */, 211 | 278B0C6D152A64A900577747 /* DBDocument.m */, 212 | 27A8A797152A01FF00363704 /* DBWindowController.h */, 213 | 27A8A798152A01FF00363704 /* DBWindowController.m */, 214 | 27F3A62E15EE98C000263663 /* QueryResultController.h */, 215 | 27F3A62F15EE98C100263663 /* QueryResultController.m */, 216 | 27F3A63115EEA06900263663 /* RevTreeController.h */, 217 | 27F3A63215EEA06900263663 /* RevTreeController.m */, 218 | 2765CF7615544FAA0087D810 /* DocEditor.h */, 219 | 2765CF7715544FAA0087D810 /* DocEditor.m */, 220 | 27A8A799152A01FF00363704 /* DBWindowController.xib */, 221 | ); 222 | name = "Database Window"; 223 | sourceTree = ""; 224 | }; 225 | /* End PBXGroup section */ 226 | 227 | /* Begin PBXNativeTarget section */ 228 | 27A8A771152A008200363704 /* Couchbase Lite Viewer */ = { 229 | isa = PBXNativeTarget; 230 | buildConfigurationList = 27A8A790152A008200363704 /* Build configuration list for PBXNativeTarget "Couchbase Lite Viewer" */; 231 | buildPhases = ( 232 | 27A8A76E152A008200363704 /* Sources */, 233 | 27A8A76F152A008200363704 /* Frameworks */, 234 | 27A8A770152A008200363704 /* Resources */, 235 | 273A438E153C935F00AEC9D7 /* Copy Frameworks */, 236 | ); 237 | buildRules = ( 238 | ); 239 | dependencies = ( 240 | ); 241 | name = "Couchbase Lite Viewer"; 242 | productName = "TouchDB Viewer"; 243 | productReference = 27A8A772152A008200363704 /* Couchbase Lite Viewer.app */; 244 | productType = "com.apple.product-type.application"; 245 | }; 246 | /* End PBXNativeTarget section */ 247 | 248 | /* Begin PBXProject section */ 249 | 27A8A769152A008100363704 /* Project object */ = { 250 | isa = PBXProject; 251 | attributes = { 252 | LastUpgradeCheck = 0710; 253 | ORGANIZATIONNAME = "Couchbase, Inc."; 254 | TargetAttributes = { 255 | 27A8A771152A008200363704 = { 256 | DevelopmentTeam = N2Q372V7W2; 257 | }; 258 | }; 259 | }; 260 | buildConfigurationList = 27A8A76C152A008100363704 /* Build configuration list for PBXProject "Couchbase Lite Viewer" */; 261 | compatibilityVersion = "Xcode 3.2"; 262 | developmentRegion = English; 263 | hasScannedForEncodings = 0; 264 | knownRegions = ( 265 | en, 266 | ); 267 | mainGroup = 27A8A767152A008100363704; 268 | productRefGroup = 27A8A773152A008200363704 /* Products */; 269 | projectDirPath = ""; 270 | projectRoot = ""; 271 | targets = ( 272 | 27A8A771152A008200363704 /* Couchbase Lite Viewer */, 273 | ); 274 | }; 275 | /* End PBXProject section */ 276 | 277 | /* Begin PBXResourcesBuildPhase section */ 278 | 27A8A770152A008200363704 /* Resources */ = { 279 | isa = PBXResourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | 27A8A781152A008200363704 /* InfoPlist.strings in Resources */, 283 | 27A8A787152A008200363704 /* Credits.rtf in Resources */, 284 | 27A8A78D152A008200363704 /* MainMenu.xib in Resources */, 285 | 27DC377E1BDE8EC100734E82 /* DBDoc.icns in Resources */, 286 | 27A8A79B152A01FF00363704 /* DBWindowController.xib in Resources */, 287 | 27DC377C1BDE8BAB00734E82 /* Images.xcassets in Resources */, 288 | 27538FB017F3607C004C3BFD /* AppListController.xib in Resources */, 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | /* End PBXResourcesBuildPhase section */ 293 | 294 | /* Begin PBXSourcesBuildPhase section */ 295 | 27A8A76E152A008200363704 /* Sources */ = { 296 | isa = PBXSourcesBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | 27A8A783152A008200363704 /* main.m in Sources */, 300 | 27A8A78A152A008200363704 /* AppDelegate.m in Sources */, 301 | 27A8A79A152A01FF00363704 /* DBWindowController.m in Sources */, 302 | 278B0C6F152A64A900577747 /* DBDocument.m in Sources */, 303 | 2765CF7815544FAA0087D810 /* DocEditor.m in Sources */, 304 | 278C85B81554810D0016C7A4 /* JSONFormatter.m in Sources */, 305 | 27F3A62D15EE70B300263663 /* DocHistory.m in Sources */, 306 | 27F3A63015EE98C100263663 /* QueryResultController.m in Sources */, 307 | 276AA6C21BD846CD00973893 /* JSONKeyFormatter.m in Sources */, 308 | 27F3A63315EEA06900263663 /* RevTreeController.m in Sources */, 309 | 27538FA917F34C09004C3BFD /* AppList.m in Sources */, 310 | 274789FF15F97520006E842D /* URLFormatter.m in Sources */, 311 | 27538FAF17F3607C004C3BFD /* AppListController.m in Sources */, 312 | 2733A75D1BD70E5600F0FF07 /* JSONItem.m in Sources */, 313 | ); 314 | runOnlyForDeploymentPostprocessing = 0; 315 | }; 316 | /* End PBXSourcesBuildPhase section */ 317 | 318 | /* Begin PBXVariantGroup section */ 319 | 27A8A77F152A008200363704 /* InfoPlist.strings */ = { 320 | isa = PBXVariantGroup; 321 | children = ( 322 | 27A8A780152A008200363704 /* en */, 323 | ); 324 | name = InfoPlist.strings; 325 | sourceTree = ""; 326 | }; 327 | 27A8A785152A008200363704 /* Credits.rtf */ = { 328 | isa = PBXVariantGroup; 329 | children = ( 330 | 27A8A786152A008200363704 /* en */, 331 | ); 332 | name = Credits.rtf; 333 | sourceTree = ""; 334 | }; 335 | 27A8A78B152A008200363704 /* MainMenu.xib */ = { 336 | isa = PBXVariantGroup; 337 | children = ( 338 | 27A8A78C152A008200363704 /* en */, 339 | ); 340 | name = MainMenu.xib; 341 | sourceTree = ""; 342 | }; 343 | /* End PBXVariantGroup section */ 344 | 345 | /* Begin XCBuildConfiguration section */ 346 | 27A8A78E152A008200363704 /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | ALWAYS_SEARCH_USER_PATHS = NO; 350 | CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; 351 | CLANG_ENABLE_OBJC_ARC = YES; 352 | CLANG_WARN_BOOL_CONVERSION = YES; 353 | CLANG_WARN_CONSTANT_CONVERSION = YES; 354 | CLANG_WARN_EMPTY_BODY = YES; 355 | CLANG_WARN_ENUM_CONVERSION = YES; 356 | CLANG_WARN_INT_CONVERSION = YES; 357 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 358 | CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES; 359 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO; 360 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; 361 | CLANG_WARN_UNREACHABLE_CODE = YES; 362 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 363 | COPY_PHASE_STRIP = NO; 364 | CURRENT_PROJECT_VERSION = 0.63; 365 | ENABLE_STRICT_OBJC_MSGSEND = YES; 366 | ENABLE_TESTABILITY = YES; 367 | GCC_C_LANGUAGE_STANDARD = gnu99; 368 | GCC_DYNAMIC_NO_PIC = NO; 369 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 370 | GCC_NO_COMMON_BLOCKS = YES; 371 | GCC_OPTIMIZATION_LEVEL = 0; 372 | GCC_PREPROCESSOR_DEFINITIONS = ( 373 | "DEBUG=1", 374 | "$(inherited)", 375 | ); 376 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 377 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 378 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 379 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 380 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 381 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 382 | GCC_WARN_UNDECLARED_SELECTOR = YES; 383 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 384 | GCC_WARN_UNUSED_FUNCTION = YES; 385 | GCC_WARN_UNUSED_VARIABLE = YES; 386 | MACOSX_DEPLOYMENT_TARGET = 10.7; 387 | ONLY_ACTIVE_ARCH = YES; 388 | SDKROOT = macosx; 389 | VERSIONING_SYSTEM = "apple-generic"; 390 | WARNING_CFLAGS = ( 391 | "-Wall", 392 | "-Wformat-security", 393 | "-Wshorten-64-to-32", 394 | "-Wmissing-declarations", 395 | "-Woverriding-method-mismatch", 396 | "-Wbool-conversion", 397 | ); 398 | }; 399 | name = Debug; 400 | }; 401 | 27A8A78F152A008200363704 /* Release */ = { 402 | isa = XCBuildConfiguration; 403 | buildSettings = { 404 | ALWAYS_SEARCH_USER_PATHS = NO; 405 | CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; 406 | CLANG_ENABLE_OBJC_ARC = YES; 407 | CLANG_WARN_BOOL_CONVERSION = YES; 408 | CLANG_WARN_CONSTANT_CONVERSION = YES; 409 | CLANG_WARN_EMPTY_BODY = YES; 410 | CLANG_WARN_ENUM_CONVERSION = YES; 411 | CLANG_WARN_INT_CONVERSION = YES; 412 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 413 | CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES; 414 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO; 415 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; 416 | CLANG_WARN_UNREACHABLE_CODE = YES; 417 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 418 | COPY_PHASE_STRIP = YES; 419 | CURRENT_PROJECT_VERSION = 0.63; 420 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 421 | ENABLE_STRICT_OBJC_MSGSEND = YES; 422 | GCC_C_LANGUAGE_STANDARD = gnu99; 423 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 424 | GCC_NO_COMMON_BLOCKS = YES; 425 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 426 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 427 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 428 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 429 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 430 | GCC_WARN_UNDECLARED_SELECTOR = YES; 431 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 432 | GCC_WARN_UNUSED_FUNCTION = YES; 433 | GCC_WARN_UNUSED_VARIABLE = YES; 434 | MACOSX_DEPLOYMENT_TARGET = 10.7; 435 | SDKROOT = macosx; 436 | VERSIONING_SYSTEM = "apple-generic"; 437 | WARNING_CFLAGS = ( 438 | "-Wall", 439 | "-Wformat-security", 440 | "-Wshorten-64-to-32", 441 | "-Wmissing-declarations", 442 | "-Woverriding-method-mismatch", 443 | "-Wbool-conversion", 444 | ); 445 | }; 446 | name = Release; 447 | }; 448 | 27A8A791152A008200363704 /* Debug */ = { 449 | isa = XCBuildConfiguration; 450 | buildSettings = { 451 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 452 | CLANG_ENABLE_OBJC_ARC = YES; 453 | CODE_SIGN_IDENTITY = ""; 454 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = ""; 455 | COMBINE_HIDPI_IMAGES = YES; 456 | FRAMEWORK_SEARCH_PATHS = ( 457 | "$(inherited)", 458 | "$(SRCROOT)/Frameworks", 459 | ); 460 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 461 | GCC_PREFIX_HEADER = "Source/Couchbase Lite Viewer-Prefix.pch"; 462 | INFOPLIST_FILE = "Source/Couchbase Lite Viewer-Info.plist"; 463 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks/"; 464 | LIBRARY_SEARCH_PATHS = ( 465 | "$(inherited)", 466 | "$(PROJECT_DIR)/Frameworks", 467 | ); 468 | MACOSX_DEPLOYMENT_TARGET = 10.9; 469 | OTHER_CODE_SIGN_FLAGS = "--deep"; 470 | PRODUCT_BUNDLE_IDENTIFIER = "com.couchbase.cblite-viewer"; 471 | PRODUCT_NAME = "Couchbase Lite Viewer"; 472 | PROVISIONING_PROFILE = ""; 473 | WRAPPER_EXTENSION = app; 474 | }; 475 | name = Debug; 476 | }; 477 | 27A8A792152A008200363704 /* Release */ = { 478 | isa = XCBuildConfiguration; 479 | buildSettings = { 480 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 481 | CLANG_ENABLE_OBJC_ARC = YES; 482 | CODE_SIGN_IDENTITY = ""; 483 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = ""; 484 | COMBINE_HIDPI_IMAGES = YES; 485 | FRAMEWORK_SEARCH_PATHS = ( 486 | "$(inherited)", 487 | "$(SRCROOT)/Frameworks", 488 | ); 489 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 490 | GCC_PREFIX_HEADER = "Source/Couchbase Lite Viewer-Prefix.pch"; 491 | INFOPLIST_FILE = "Source/Couchbase Lite Viewer-Info.plist"; 492 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks/"; 493 | LIBRARY_SEARCH_PATHS = ( 494 | "$(inherited)", 495 | "$(PROJECT_DIR)/Frameworks", 496 | ); 497 | MACOSX_DEPLOYMENT_TARGET = 10.9; 498 | OTHER_CODE_SIGN_FLAGS = "--deep"; 499 | PRODUCT_BUNDLE_IDENTIFIER = "com.couchbase.cblite-viewer"; 500 | PRODUCT_NAME = "Couchbase Lite Viewer"; 501 | PROVISIONING_PROFILE = ""; 502 | WRAPPER_EXTENSION = app; 503 | }; 504 | name = Release; 505 | }; 506 | /* End XCBuildConfiguration section */ 507 | 508 | /* Begin XCConfigurationList section */ 509 | 27A8A76C152A008100363704 /* Build configuration list for PBXProject "Couchbase Lite Viewer" */ = { 510 | isa = XCConfigurationList; 511 | buildConfigurations = ( 512 | 27A8A78E152A008200363704 /* Debug */, 513 | 27A8A78F152A008200363704 /* Release */, 514 | ); 515 | defaultConfigurationIsVisible = 0; 516 | defaultConfigurationName = Release; 517 | }; 518 | 27A8A790152A008200363704 /* Build configuration list for PBXNativeTarget "Couchbase Lite Viewer" */ = { 519 | isa = XCConfigurationList; 520 | buildConfigurations = ( 521 | 27A8A791152A008200363704 /* Debug */, 522 | 27A8A792152A008200363704 /* Release */, 523 | ); 524 | defaultConfigurationIsVisible = 0; 525 | defaultConfigurationName = Release; 526 | }; 527 | /* End XCConfigurationList section */ 528 | }; 529 | rootObject = 27A8A769152A008100363704 /* Project object */; 530 | } 531 | -------------------------------------------------------------------------------- /Couchbase Lite Viewer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Couchbase Lite Viewer.xcodeproj/project.xcworkspace/xcshareddata/Couchbase Lite Viewer.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 603E2B31-5014-4219-A23A-468ACE361684 9 | IDESourceControlProjectName 10 | Couchbase Lite Viewer 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | CDABB946C216D03B6362B4E160EDF17410CB25BE 14 | github.com:couchbaselabs/TouchDBViewer.git 15 | 16 | IDESourceControlProjectPath 17 | Couchbase Lite Viewer.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | CDABB946C216D03B6362B4E160EDF17410CB25BE 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | github.com:couchbaselabs/TouchDBViewer.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | CDABB946C216D03B6362B4E160EDF17410CB25BE 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | CDABB946C216D03B6362B4E160EDF17410CB25BE 36 | IDESourceControlWCCName 37 | CouchbaseLiteViewer 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Couchbase Lite Viewer.xcodeproj/project.xcworkspace/xcshareddata/TouchDB Viewer.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectIdentifier 6 | DFF631CE-7DE6-44EC-8692-4766B948B6FC 7 | IDESourceControlProjectName 8 | TouchDB Viewer 9 | IDESourceControlProjectOriginsDictionary 10 | 11 | 31C8F644-06C2-4F41-B1E3-A50F62BEAC01 12 | ssh://github.com/couchbaselabs/CouchCocoa.git 13 | 71AB55E9-9522-47E7-A129-650B4443A625 14 | ssh://github.com/couchbaselabs/TouchDBViewer.git 15 | FF89FF57-65E4-43D3-B2FE-484A478DD730 16 | ssh://github.com/couchbaselabs/TouchDB-iOS.git 17 | 18 | IDESourceControlProjectPath 19 | TouchDB Viewer.xcodeproj/project.xcworkspace 20 | IDESourceControlProjectRelativeInstallPathDictionary 21 | 22 | 31C8F644-06C2-4F41-B1E3-A50F62BEAC01 23 | ../../../CouchCocoa 24 | 71AB55E9-9522-47E7-A129-650B4443A625 25 | ../.. 26 | FF89FF57-65E4-43D3-B2FE-484A478DD730 27 | ../../../TouchDB 28 | 29 | IDESourceControlProjectURL 30 | ssh://github.com/couchbaselabs/TouchDBViewer.git 31 | IDESourceControlProjectVersion 32 | 110 33 | IDESourceControlProjectWCCIdentifier 34 | 71AB55E9-9522-47E7-A129-650B4443A625 35 | IDESourceControlProjectWCConfigurations 36 | 37 | 38 | IDESourceControlRepositoryExtensionIdentifierKey 39 | public.vcs.git 40 | IDESourceControlWCCIdentifierKey 41 | 71AB55E9-9522-47E7-A129-650B4443A625 42 | IDESourceControlWCCName 43 | TouchDB Viewer 44 | 45 | 46 | IDESourceControlRepositoryExtensionIdentifierKey 47 | public.vcs.git 48 | IDESourceControlWCCIdentifierKey 49 | 31C8F644-06C2-4F41-B1E3-A50F62BEAC01 50 | IDESourceControlWCCName 51 | CouchCocoa 52 | 53 | 54 | IDESourceControlRepositoryExtensionIdentifierKey 55 | public.vcs.git 56 | IDESourceControlWCCIdentifierKey 57 | FF89FF57-65E4-43D3-B2FE-484A478DD730 58 | IDESourceControlWCCName 59 | TouchDB 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Couchbase Lite Viewer/DBDoc.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/CouchbaseLiteViewer/d934b42aae5b2ae2ca97a0203dc949ba002a323a/Couchbase Lite Viewer/DBDoc.icns -------------------------------------------------------------------------------- /Couchbase Lite Viewer/Images.xcassets/AppIcon.appiconset/AppIcon128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/CouchbaseLiteViewer/d934b42aae5b2ae2ca97a0203dc949ba002a323a/Couchbase Lite Viewer/Images.xcassets/AppIcon.appiconset/AppIcon128x128.png -------------------------------------------------------------------------------- /Couchbase Lite Viewer/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "size" : "128x128", 25 | "idiom" : "mac", 26 | "filename" : "AppIcon128x128.png", 27 | "scale" : "1x" 28 | }, 29 | { 30 | "idiom" : "mac", 31 | "scale" : "2x", 32 | "size" : "128x128" 33 | }, 34 | { 35 | "idiom" : "mac", 36 | "scale" : "1x", 37 | "size" : "256x256" 38 | }, 39 | { 40 | "idiom" : "mac", 41 | "scale" : "2x", 42 | "size" : "256x256" 43 | }, 44 | { 45 | "idiom" : "mac", 46 | "scale" : "1x", 47 | "size" : "512x512" 48 | }, 49 | { 50 | "idiom" : "mac", 51 | "scale" : "2x", 52 | "size" : "512x512" 53 | } 54 | ], 55 | "info" : { 56 | "version" : 1, 57 | "author" : "xcode" 58 | } 59 | } -------------------------------------------------------------------------------- /Couchbase Lite Viewer/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Couchbase Lite Viewer/Images.xcassets/database.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "database.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Couchbase Lite Viewer/Images.xcassets/database.imageset/database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/CouchbaseLiteViewer/d934b42aae5b2ae2ca97a0203dc949ba002a323a/Couchbase Lite Viewer/Images.xcassets/database.imageset/database.png -------------------------------------------------------------------------------- /Couchbase Lite Viewer/Images.xcassets/database2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "database2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Couchbase Lite Viewer/Images.xcassets/database2.imageset/database2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/CouchbaseLiteViewer/d934b42aae5b2ae2ca97a0203dc949ba002a323a/Couchbase Lite Viewer/Images.xcassets/database2.imageset/database2.png -------------------------------------------------------------------------------- /Couchbase Lite Viewer/Images.xcassets/ios_app.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ios_app.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Couchbase Lite Viewer/Images.xcassets/ios_app.imageset/ios_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/CouchbaseLiteViewer/d934b42aae5b2ae2ca97a0203dc949ba002a323a/Couchbase Lite Viewer/Images.xcassets/ios_app.imageset/ios_app.png -------------------------------------------------------------------------------- /Frameworks/README.md: -------------------------------------------------------------------------------- 1 | Copy or symlink CouchbaseLite.framework (for Mac OS) into this directory. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️ This repo is obsolete and the new preferred method for viewing databases is the [Visual Studio Code Plugin](https://github.com/couchbaselabs/vscode-cblite). 2 | 3 | This is a fairly simple Mac editor for [Couchbase Lite](https://github.com/couchbase/CouchbaseLite-iOS) databases. 4 | 5 | ## Features 6 | 7 | 0. Browse the databases of all iOS apps in the Simulator, and all Mac apps. 8 | 1. Open any Couchbase Lite database file on a reachable filesystem 9 | 2. View all documents including revision IDs and sequence numbers 10 | 3. View and modify JSON properties of any document 11 | 4. View document history as a full revision tree (by double-clicking a document row) 12 | 5. Add and remove documents 13 | 14 | ## Requirements 15 | 16 | * Mac OS X 10.7+. 17 | * Xcode 6+ to build it. 18 | 19 | ## Building It 20 | 21 | * Get [Couchbase Lite](http://www.couchbase.com/nosql-databases/downloads) 22 | * Copy or symlink Couchbase Lite.framework into the `Frameworks/` subdirectory. 23 | * Open `Couchbase Lite Viewer.xcodeproj`. 24 | * Product > Build 25 | 26 | ## Using It 27 | 28 | Use the browser window to find your app. Mac apps are in one group, iOS apps are grouped by simulator type. 29 | 30 | Once you select your app you'll see its databases; double-click one to open it. 31 | 32 | The database viewer window lists all the documents in the left pane, and shows the properties of the selected document in the right pane. (You can add any property you want as a column in the left pane by right-clicking it in the right pane and selecting "Add Column".) 33 | 34 | Should you want to view the revision history of a document, double-click it in the left pane. Click the navigation control at the top to return to the document list. 35 | -------------------------------------------------------------------------------- /Source/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 4/2/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : NSObject 12 | 13 | @property (weak) IBOutlet NSWindow *window; 14 | @property (weak) IBOutlet NSPanel* urlPanel; 15 | @property (weak) IBOutlet NSTextField *urlInputField; 16 | 17 | - (IBAction) showAppBrowser: (id)sender; 18 | 19 | //- (IBAction) dismissURLPanel:(id)sender; 20 | @end 21 | -------------------------------------------------------------------------------- /Source/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 4/2/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "DBWindowController.h" 11 | #import "AppListController.h" 12 | #import 13 | 14 | 15 | @implementation AppDelegate 16 | 17 | @synthesize window=_window, urlPanel=_urlPanel, urlInputField=_urlInputField; 18 | 19 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 20 | { 21 | [AppListController restore]; 22 | } 23 | 24 | - (BOOL) applicationShouldOpenUntitledFile:(NSApplication *)sender { 25 | return NO; 26 | } 27 | 28 | - (IBAction) showAppBrowser: (id)sender { 29 | [AppListController show]; 30 | } 31 | 32 | - (IBAction) orderFrontStandardAboutPanel:(id)sender { 33 | NSString* cblVers = [NSString stringWithFormat: @"Couchbase Lite %@", CBLVersion()]; 34 | [NSApp orderFrontStandardAboutPanelWithOptions: @{@"Version": cblVers}]; 35 | } 36 | 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Source/AppList.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppList.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 9/25/13. 6 | // Copyright (c) 2013 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | typedef enum AppListNodeType { 13 | kOSNode, 14 | kAppNode, 15 | kDbNode 16 | } AppListNodeType; 17 | 18 | 19 | /** Model for a tree node in the AppListController's outline view */ 20 | @interface AppListNode : NSObject 21 | 22 | @property (readonly) AppListNodeType type; 23 | @property BOOL isMacOS; 24 | @property (copy) NSString* bundleID; 25 | @property (readonly, nonatomic) NSString* displayName; 26 | @property (readonly, nonatomic) NSString* path; 27 | @property (readonly) NSImage* icon; 28 | @property (readonly) NSArray* nameAndIcon; 29 | 30 | @property (readonly) NSMutableArray* children; 31 | 32 | @end 33 | 34 | 35 | AppListNode* BuildAppList(NSError** outError); 36 | void TestAppList(void); 37 | -------------------------------------------------------------------------------- /Source/AppList.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppList.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 9/25/13. 6 | // Copyright (c) 2013 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "AppList.h" 10 | 11 | 12 | #define kSimulatorAppID @"com.apple.iphonesimulator" 13 | #define kIconSize 22 14 | 15 | 16 | @implementation AppListNode 17 | { 18 | AppListNodeType _type; 19 | NSString* _displayName; 20 | NSString* _path; 21 | NSMutableArray* _children; 22 | NSImage* _appIcon; 23 | } 24 | 25 | 26 | NSImage* kiOSIcon, *kMacOSIcon, *kAppIcon, *kMacAppIcon, *kDbIcon, *kDb2Icon; 27 | 28 | 29 | + (void) initialize { 30 | if (self == [AppListNode class]) { 31 | NSString* simulatorPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier: kSimulatorAppID]; 32 | kiOSIcon = [[[NSWorkspace sharedWorkspace] iconForFile: simulatorPath] copy]; 33 | kiOSIcon.size = NSMakeSize(kIconSize, kIconSize); 34 | 35 | kMacOSIcon = [[[NSWorkspace sharedWorkspace] iconForFile: @"/System/Library/CoreServices/Finder.app"] copy]; 36 | kMacOSIcon.size = NSMakeSize(kIconSize, kIconSize); 37 | 38 | kAppIcon = [[NSImage imageNamed: @"ios_app"] copy]; 39 | kAppIcon.size = NSMakeSize(kIconSize, kIconSize); 40 | 41 | kMacAppIcon = [[NSWorkspace sharedWorkspace] iconForFileType: NSFileTypeForHFSTypeCode('APPL')]; 42 | 43 | kDbIcon = [[NSImage imageNamed: @"database"] copy]; 44 | kDbIcon.size = NSMakeSize(kIconSize, kIconSize); 45 | 46 | kDb2Icon = [[NSImage imageNamed: @"database2"] copy]; 47 | kDb2Icon.size = NSMakeSize(kIconSize, kIconSize); 48 | } 49 | } 50 | 51 | @synthesize type=_type, path=_path, displayName=_displayName, children=_children; 52 | @synthesize isMacOS=_isMacOS, bundleID=_bundleID; 53 | 54 | - (id) initWithType: (AppListNodeType)type path: (NSString*)path displayName: (NSString*)displayName { 55 | self = [super init]; 56 | if (self) { 57 | _type = type; 58 | _displayName = displayName.copy; 59 | _path = path.copy; 60 | _children = [NSMutableArray array]; 61 | } 62 | return self; 63 | } 64 | 65 | - (NSImage*) icon { 66 | switch (_type) { 67 | case kOSNode: 68 | return _isMacOS ?kMacOSIcon : kiOSIcon; 69 | case kAppNode: 70 | if (!_appIcon) 71 | _appIcon = [self findAppIcon] ?: (_isMacOS ? kMacAppIcon : kAppIcon); 72 | return _appIcon; 73 | case kDbNode: 74 | if ([_path.pathExtension isEqualToString: @"cblite"]) 75 | return kDbIcon; 76 | else 77 | return kDb2Icon; 78 | } 79 | } 80 | 81 | - (NSArray*) nameAndIcon { 82 | return @[self.displayName, self.icon]; 83 | } 84 | 85 | - (NSImage*) findAppIcon { 86 | NSImage* icon = nil; 87 | #if 1 88 | if (_isMacOS) { 89 | NSString* appPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier: _bundleID]; 90 | if (appPath) 91 | icon = [[NSWorkspace sharedWorkspace] iconForFile: appPath]; 92 | } 93 | #else 94 | NSString* bundlePath = [self.path stringByAppendingPathComponent: getIOSAppBundleID(self.path)]; 95 | NSString* infoPath = [bundlePath stringByAppendingPathComponent: @"Info.plist"]; 96 | NSDictionary* plist = [NSDictionary dictionaryWithContentsOfFile: infoPath]; 97 | NSString* iconFileName = plist[@"CFBundleIcons"][@"CFBundlePrimaryIcon"][@"CFBundleIconFiles"][0]; 98 | if (!iconFileName) 99 | return nil; 100 | NSString* iconPath = [bundlePath stringByAppendingPathComponent: iconFileName]; 101 | icon = [[NSImage alloc] initByReferencingFile: iconPath]; 102 | #endif 103 | if (!icon) 104 | icon = (_isMacOS ? kMacAppIcon : kAppIcon); 105 | icon.size = NSMakeSize(kIconSize, kIconSize); 106 | return icon; 107 | } 108 | 109 | @end 110 | 111 | 112 | #define kSimulatorPath @"Library/Developer/CoreSimulator/Devices/" 113 | #define kDbDirName @"CouchbaseLite" 114 | #define kIOSDbDirPath @"Library/Application Support/CouchbaseLite" 115 | #define kDbPathExtensions @[@"cblite", @"cblite2"] 116 | #define kReplicatorDbName @"_replicator" // old persistent-replications database, pre-1.0 117 | 118 | 119 | static NSArray* sortedKeys(NSDictionary* dict) { 120 | return [dict.allKeys sortedArrayUsingSelector: @selector(localizedCaseInsensitiveCompare:)]; 121 | } 122 | 123 | 124 | // Returns a dictionary mapping display-names -> absolute paths. 125 | // Block is given a filename and returns a display-name or nil. 126 | static NSMutableDictionary* iterateDir(NSString* dir, NSError** outError, 127 | NSString* (^block)(NSString* path, NSString* filename)) 128 | { 129 | NSArray* filenames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: dir 130 | error: outError]; 131 | if (!filenames) { 132 | if ([[*outError domain] isEqualToString: NSCocoaErrorDomain] && [*outError code] == NSFileReadNoSuchFileError) { 133 | *outError = nil; 134 | return [NSMutableDictionary new]; 135 | } 136 | return nil; 137 | } 138 | 139 | NSMutableDictionary* result = [NSMutableDictionary dictionary]; 140 | for (NSString* filename in filenames) { 141 | if ([filename hasPrefix: @"."]) 142 | continue; 143 | NSString* path = [dir stringByAppendingPathComponent: filename]; 144 | NSString* key = block(path, filename); 145 | if (key) 146 | result[key] = path; 147 | } 148 | return result; 149 | } 150 | 151 | 152 | #pragma mark - iOS: 153 | 154 | 155 | static NSDictionary* findIOSSimulatorDirs(NSError** error) { 156 | NSString* dir = [NSHomeDirectory() stringByAppendingPathComponent: kSimulatorPath]; 157 | return iterateDir(dir, error, ^NSString *(NSString* path, NSString *dirname) { 158 | NSString* infoPath = [path stringByAppendingPathComponent: @"device.plist"]; 159 | NSDictionary* info = [NSDictionary dictionaryWithContentsOfFile: infoPath]; 160 | NSString* name = info[@"name"]; 161 | NSString* version = [info[@"runtime"] componentsSeparatedByString: @"."].lastObject; 162 | if (version) { 163 | NSArray* c = [version componentsSeparatedByString: @"-"]; 164 | name = [name stringByAppendingFormat: @" (%@ %@.%@)", c[0], c[1], c[2]]; 165 | } 166 | return name; 167 | }); 168 | } 169 | 170 | 171 | static NSString* getIOSAppBundleID(NSString* appHomeDir) { 172 | NSString* infoPath = [appHomeDir stringByAppendingPathComponent: @".com.apple.mobile_container_manager.metadata.plist"]; 173 | NSDictionary* info = [NSDictionary dictionaryWithContentsOfFile: infoPath]; 174 | NSString* appBundleID = info[@"MCMMetadataIdentifier"]; 175 | return appBundleID; 176 | } 177 | 178 | 179 | static NSString* getIOSAppBundleName(NSString* appHomeDir, NSDictionary* deviceInfo) { 180 | NSString* bundleID = getIOSAppBundleID(appHomeDir); 181 | NSDictionary* info = deviceInfo[@"User"]; 182 | info = info[bundleID]; 183 | NSString* name = [[info[@"Path"] lastPathComponent] stringByDeletingPathExtension]; 184 | return name ?: bundleID; 185 | } 186 | 187 | 188 | static NSDictionary* findIOSAppDirs(NSString* osDir, NSError** error) { 189 | NSString* deviceInfoPath = [osDir stringByAppendingPathComponent: @"data/Library/MobileInstallation/LastLaunchServicesMap.plist"]; 190 | NSDictionary* deviceInfo = [NSDictionary dictionaryWithContentsOfFile: deviceInfoPath]; 191 | 192 | NSString* appsDir = [osDir stringByAppendingPathComponent: @"data/Containers/Data/Application"]; 193 | return iterateDir(appsDir, error, ^NSString *(NSString* appHomeDir, NSString *dirName) { 194 | NSString* cblPath = [appHomeDir stringByAppendingPathComponent: kIOSDbDirPath]; 195 | if (![[NSFileManager defaultManager] fileExistsAtPath: cblPath]) 196 | return nil; 197 | return getIOSAppBundleName(appHomeDir, deviceInfo); 198 | }); 199 | } 200 | 201 | 202 | static NSDictionary* findIOSAppDatabases(NSString* appHomeDir, NSError** error) { 203 | NSString* cblPath = [appHomeDir stringByAppendingPathComponent: kIOSDbDirPath]; 204 | return iterateDir(cblPath, error, ^NSString *(NSString* path, NSString *filename) { 205 | if (![kDbPathExtensions containsObject: filename.pathExtension]) 206 | return nil; 207 | NSString* dbName = filename.stringByDeletingPathExtension; 208 | if ([dbName isEqualToString: kReplicatorDbName]) 209 | return nil; 210 | return [dbName stringByReplacingOccurrencesOfString: @":" withString: @"/"]; 211 | }); 212 | } 213 | 214 | 215 | static BOOL buildIOSAppTree(AppListNode *root, NSError** outError) { 216 | NSDictionary* versions = findIOSSimulatorDirs(outError); 217 | if (!versions) 218 | return NO; 219 | for (NSString* version in sortedKeys(versions)) { 220 | AppListNode* versNode = [[AppListNode alloc] initWithType: kOSNode path: versions[version] displayName: version]; 221 | 222 | NSDictionary* apps = findIOSAppDirs(versions[version], outError); 223 | if (!apps) 224 | return NO; 225 | for (NSString* app in sortedKeys(apps)) { 226 | AppListNode* appNode = [[AppListNode alloc] initWithType: kAppNode path: apps[app] displayName: app]; 227 | appNode.bundleID = app; 228 | 229 | NSDictionary* dbs = findIOSAppDatabases(apps[app], outError); 230 | if (!dbs) 231 | return NO; 232 | for (NSString* db in sortedKeys(dbs)) { 233 | AppListNode* dbNode = [[AppListNode alloc] initWithType: kDbNode path: dbs[db] displayName: db]; 234 | [appNode.children addObject: dbNode]; 235 | } 236 | if (appNode.children.count > 0) 237 | [versNode.children addObject: appNode]; 238 | } 239 | if (versNode.children.count > 0) 240 | [root.children insertObject: versNode atIndex: 0]; // reverse order (newest OS first) 241 | } 242 | return YES; 243 | } 244 | 245 | 246 | #pragma mark - OLD iOS (pre-Xcode 7) 247 | 248 | 249 | #define kOldSimulatorPath @"Library/Application Support/iPhone Simulator/" 250 | 251 | 252 | static NSDictionary* findOldIOSSimulatorDirs(NSError** error) { 253 | NSString* dir = [NSHomeDirectory() stringByAppendingPathComponent: kOldSimulatorPath]; 254 | return iterateDir(dir, error, ^NSString *(NSString *path, NSString *filename) { 255 | if (isdigit([filename characterAtIndex: 0]) && [filename doubleValue] >= 6.0) 256 | return [NSString stringWithFormat: @"iOS %@", filename]; 257 | return nil; 258 | }); 259 | } 260 | 261 | 262 | static NSString* getOldIOSAppBundleName(NSString* appHomeDir) { 263 | NSArray* appBundles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: appHomeDir 264 | error: NULL]; 265 | appBundles = [appBundles pathsMatchingExtensions: @[@"app"]]; 266 | return appBundles.count > 0 ? appBundles[0] : nil; 267 | } 268 | 269 | 270 | static NSDictionary* findOldIOSAppDirs(NSString* osDir, NSError** error) { 271 | NSString* appsDir = [osDir stringByAppendingPathComponent: @"Applications"]; 272 | return iterateDir(appsDir, error, ^NSString *(NSString *path, NSString *dirName) { 273 | NSString* appHomeDir = [appsDir stringByAppendingPathComponent: dirName]; 274 | NSString* cblPath = [appHomeDir stringByAppendingPathComponent: kIOSDbDirPath]; 275 | if (![[NSFileManager defaultManager] fileExistsAtPath: cblPath]) 276 | return nil; 277 | return [getOldIOSAppBundleName(appHomeDir) stringByDeletingPathExtension]; 278 | }); 279 | } 280 | 281 | 282 | static NSDictionary* findOldIOSAppDatabases(NSString* appHomeDir, NSError** error) { 283 | NSString* cblPath = [appHomeDir stringByAppendingPathComponent: kIOSDbDirPath]; 284 | return iterateDir(cblPath, error, ^NSString *(NSString *path, NSString *filename) { 285 | if (![kDbPathExtensions containsObject: filename.pathExtension]) 286 | return nil; 287 | NSString* dbName = filename.stringByDeletingPathExtension; 288 | if ([dbName isEqualToString: kReplicatorDbName]) 289 | return nil; 290 | return [dbName stringByReplacingOccurrencesOfString: @":" withString: @"/"]; 291 | }); 292 | } 293 | 294 | 295 | static BOOL buildOldIOSAppTree(AppListNode* root, NSError **outError) { 296 | NSDictionary* versions = findOldIOSSimulatorDirs(outError); 297 | if (!versions) 298 | return NO; 299 | for (NSString* version in sortedKeys(versions)) { 300 | AppListNode* versNode = [[AppListNode alloc] initWithType: kOSNode path: versions[version] displayName: version]; 301 | 302 | NSDictionary* apps = findOldIOSAppDirs(versions[version], outError); 303 | if (!apps) 304 | return NO; 305 | for (NSString* app in sortedKeys(apps)) { 306 | AppListNode* appNode = [[AppListNode alloc] initWithType: kAppNode path: apps[app] displayName: app]; 307 | appNode.bundleID = app; 308 | 309 | NSDictionary* dbs = findOldIOSAppDatabases(apps[app], outError); 310 | if (!dbs) 311 | return NO; 312 | for (NSString* db in sortedKeys(dbs)) { 313 | AppListNode* dbNode = [[AppListNode alloc] initWithType: kDbNode path: dbs[db] displayName: db]; 314 | [appNode.children addObject: dbNode]; 315 | } 316 | if (appNode.children.count > 0) 317 | [versNode.children addObject: appNode]; 318 | } 319 | if (versNode.children.count > 0) 320 | [root.children insertObject: versNode atIndex: 0]; // reverse order (newest OS first) 321 | } 322 | return YES; 323 | } 324 | 325 | 326 | #pragma mark - MAC OS: 327 | 328 | 329 | static NSDictionary* findMacAppDirs(NSError** error) { 330 | NSString* dirName = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0]; 331 | NSMutableDictionary* dirs = iterateDir(dirName, error, ^NSString *(NSString* appDirPath, NSString *appDirName) { 332 | NSString* cblPath = [appDirPath stringByAppendingPathComponent: kDbDirName]; 333 | BOOL isDir; 334 | if (![[NSFileManager defaultManager] fileExistsAtPath: cblPath isDirectory: &isDir] 335 | || !isDir) 336 | return nil; 337 | return [[appDirName componentsSeparatedByString: @"."] lastObject]; 338 | }); 339 | 340 | // Now look for sandboxed apps: 341 | dirName = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0]; 342 | dirName = [dirName stringByAppendingPathComponent: @"Containers"]; 343 | 344 | NSArray* filenames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: dirName 345 | error: NULL]; 346 | for (NSString* appDirName in filenames) { 347 | NSString* appDirPath = [[[dirName stringByAppendingPathComponent: appDirName] 348 | stringByAppendingPathComponent: @"Data/Library/Application Support"] 349 | stringByAppendingPathComponent: appDirName]; 350 | NSString* cblPath = [appDirPath stringByAppendingPathComponent: kDbDirName]; 351 | BOOL isDir; 352 | if ([[NSFileManager defaultManager] fileExistsAtPath: cblPath isDirectory: &isDir] 353 | && isDir) { 354 | NSString* displayName = [[appDirName componentsSeparatedByString: @"."] lastObject]; 355 | dirs[displayName] = appDirPath; 356 | } 357 | } 358 | 359 | return dirs; 360 | } 361 | 362 | 363 | static NSDictionary* findMacAppDatabases(NSString* appSupportDir, NSError** error) { 364 | NSString* cblPath = [appSupportDir stringByAppendingPathComponent: kDbDirName]; 365 | return iterateDir(cblPath, error, ^NSString *(NSString* path, NSString *filename) { 366 | if (![kDbPathExtensions containsObject: filename.pathExtension]) 367 | return nil; 368 | NSString* dbName = filename.stringByDeletingPathExtension; 369 | if ([dbName isEqualToString: kReplicatorDbName]) 370 | return nil; 371 | return [dbName stringByReplacingOccurrencesOfString: @":" withString: @"/"]; 372 | }); 373 | } 374 | 375 | 376 | #pragma mark - MAIN: 377 | 378 | 379 | AppListNode* BuildAppList(NSError** outError) { 380 | AppListNode* root = [[AppListNode alloc] initWithType: kOSNode path: nil displayName: nil]; 381 | if (!buildOldIOSAppTree(root, outError)) 382 | return nil; 383 | if (!buildIOSAppTree(root, outError)) 384 | return nil; 385 | 386 | // Now find Mac apps: 387 | AppListNode* versNode = [[AppListNode alloc] initWithType: kOSNode path: @"" displayName: @"Mac OS"]; 388 | versNode.isMacOS = YES; 389 | NSDictionary* apps = findMacAppDirs(outError); 390 | if (!apps) 391 | return nil; 392 | for (NSString* app in sortedKeys(apps)) { 393 | AppListNode* appNode = [[AppListNode alloc] initWithType: kAppNode path: apps[app] displayName: app]; 394 | appNode.isMacOS = YES; 395 | appNode.bundleID = [apps[app] lastPathComponent]; 396 | 397 | NSDictionary* dbs = findMacAppDatabases(apps[app], outError); 398 | if (dbs) { 399 | for (NSString* db in sortedKeys(dbs)) { 400 | AppListNode* dbNode = [[AppListNode alloc] initWithType: kDbNode path: dbs[db] displayName: db]; 401 | [appNode.children addObject: dbNode]; 402 | } 403 | if (appNode.children.count > 0) 404 | [versNode.children addObject: appNode]; 405 | } 406 | } 407 | if (versNode.children.count > 0) 408 | [root.children addObject: versNode]; 409 | 410 | return root; 411 | } 412 | 413 | 414 | void TestAppList(void) { 415 | NSError* error; 416 | NSDictionary* versions = findIOSSimulatorDirs(&error); 417 | NSCAssert(versions, @"error %@", error); 418 | for (NSString* version in sortedKeys(versions)) { 419 | NSLog(@"%@:", version); 420 | NSDictionary* apps = findIOSAppDirs(versions[version], &error); 421 | NSCAssert(apps, @"error %@", error); 422 | for (NSString* app in sortedKeys(apps)) { 423 | NSLog(@"\t%@:", app); 424 | NSDictionary* dbs = findIOSAppDatabases(apps[app], &error); 425 | NSCAssert(dbs, @"error %@", error); 426 | for (NSString* db in sortedKeys(dbs)) { 427 | NSLog(@"\t\t%@", db); 428 | } 429 | } 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /Source/AppListController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppListController.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 9/25/13. 6 | // Copyright (c) 2013 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** Controller for the window that lists all the applications and their databases */ 12 | @interface AppListController : NSWindowController 13 | 14 | + (void) show; 15 | + (void) restore; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Source/AppListController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppListController.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 9/25/13. 6 | // Copyright (c) 2013 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "AppListController.h" 10 | #import "AppList.h" 11 | 12 | 13 | @interface AppListBrowserCell : NSBrowserCell 14 | @end 15 | 16 | @implementation AppListBrowserCell 17 | - (void) setObjectValue: (id)value { 18 | if ([value isKindOfClass:[NSArray class]]) { 19 | [self setStringValue: value[0]]; 20 | [self setImage: value[1]]; 21 | self.leaf = YES; // cell draws incorrectly (double triangle) unless I set this (why?!) 22 | } else { 23 | [super setObjectValue:value]; 24 | } 25 | } 26 | 27 | @end 28 | 29 | 30 | 31 | 32 | @interface AppListController () 33 | { 34 | IBOutlet NSBrowser* _browser; 35 | 36 | AppListNode* _root; 37 | } 38 | @end 39 | 40 | @implementation AppListController 41 | 42 | 43 | static AppListController* sInstance; 44 | 45 | 46 | + (void) _show { 47 | if (!sInstance) 48 | sInstance = [[self alloc] initWithWindowNibName: @"AppListController"]; 49 | [sInstance showWindow: self]; 50 | } 51 | 52 | + (void) show { 53 | [self _show]; 54 | [[NSUserDefaults standardUserDefaults] setBool: YES forKey: @"AppListShowing"]; 55 | } 56 | 57 | 58 | + (void) restore { 59 | if (![[NSUserDefaults standardUserDefaults] objectForKey: @"AppListShowing"] 60 | || [[NSUserDefaults standardUserDefaults] boolForKey: @"AppListShowing"]) 61 | [self _show]; 62 | } 63 | 64 | 65 | - (id)initWithWindow:(NSWindow *)window 66 | { 67 | self = [super initWithWindow:window]; 68 | if (self) { 69 | NSError* error; 70 | _root = BuildAppList(&error); 71 | } 72 | return self; 73 | } 74 | 75 | - (void)windowDidLoad 76 | { 77 | [super windowDidLoad]; 78 | 79 | _browser.delegate = self; 80 | _browser.cellClass = [AppListBrowserCell class]; 81 | _browser.rowHeight = 24.0; 82 | _browser.titled = YES; 83 | _browser.takesTitleFromPreviousColumn = NO; 84 | 85 | _browser.target = self; 86 | _browser.doubleAction = @selector(openItem:); 87 | } 88 | 89 | 90 | - (BOOL) windowShouldClose: (id)sender { 91 | [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"AppListShowing"]; 92 | return YES; 93 | } 94 | 95 | - (void) windowWillClose: (NSNotification*)n { 96 | if (self == sInstance) { 97 | sInstance = nil; 98 | } 99 | } 100 | 101 | 102 | - (IBAction) openItem: (id)sender { 103 | NSIndexPath* path = [_browser selectionIndexPath]; 104 | if (!path) 105 | return; 106 | AppListNode* item = [_browser itemAtIndexPath: path]; 107 | NSURL* url = [NSURL fileURLWithPath: item.path]; 108 | if (item.type == kDbNode) { 109 | [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL: url 110 | display: YES 111 | completionHandler: 112 | ^(NSDocument * document, BOOL documentWasAlreadyOpen, NSError *error) { 113 | if (error) 114 | [self presentError: error]; 115 | }]; 116 | } 117 | } 118 | 119 | 120 | - (IBAction) revealInFinder: (id)sender { 121 | AppListNode* item = [_browser itemAtRow: _browser.clickedRow inColumn: _browser.clickedColumn]; 122 | NSString* select = item.path; 123 | NSString* windowPath; 124 | if (item.type == kDbNode) { 125 | windowPath = select.stringByDeletingLastPathComponent; 126 | } else { 127 | windowPath = select; 128 | select = nil; 129 | } 130 | [[NSWorkspace sharedWorkspace] selectFile: select 131 | inFileViewerRootedAtPath: windowPath]; 132 | } 133 | 134 | 135 | - (id)rootItemForBrowser:(NSBrowser *)browser{ 136 | return _root; 137 | } 138 | 139 | - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item { 140 | return ((AppListNode*)item).children.count; 141 | } 142 | 143 | 144 | - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item { 145 | return ((AppListNode*)item).children[index]; 146 | } 147 | 148 | 149 | - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item { 150 | return ((AppListNode*)item).type == kDbNode; 151 | } 152 | 153 | 154 | - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item{ 155 | return ((AppListNode*)item).nameAndIcon; 156 | } 157 | 158 | - (NSString*) browser:(NSBrowser *)sender titleOfColumn:(NSInteger)column { 159 | return @[@"Platforms", @"Apps", @"Databases"][column]; 160 | } 161 | 162 | 163 | @end 164 | -------------------------------------------------------------------------------- /Source/AppListController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Source/Couchbase Lite Viewer-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeExtensions 11 | 12 | cblite2 13 | 14 | CFBundleTypeIconFile 15 | DBDoc 16 | CFBundleTypeName 17 | Couchbase Lite database 18 | CFBundleTypeRole 19 | Editor 20 | LSTypeIsPackage 21 | 1 22 | NSDocumentClass 23 | DBDocument 24 | 25 | 26 | CFBundleTypeExtensions 27 | 28 | cblite 29 | 30 | CFBundleTypeIconFile 31 | DBDoc 32 | CFBundleTypeName 33 | Couchbase Lite database 34 | CFBundleTypeRole 35 | Editor 36 | LSTypeIsPackage 37 | 0 38 | NSDocumentClass 39 | DBDocument 40 | 41 | 42 | CFBundleExecutable 43 | ${EXECUTABLE_NAME} 44 | CFBundleIdentifier 45 | $(PRODUCT_BUNDLE_IDENTIFIER) 46 | CFBundleInfoDictionaryVersion 47 | 6.0 48 | CFBundleName 49 | ${PRODUCT_NAME} 50 | CFBundlePackageType 51 | APPL 52 | CFBundleShortVersionString 53 | ${CURRENT_PROJECT_VERSION} 54 | CFBundleSignature 55 | ???? 56 | CFBundleVersion 57 | ${CURRENT_PROJECT_VERSION} 58 | LSApplicationCategoryType 59 | public.app-category.developer-tools 60 | LSMinimumSystemVersion 61 | ${MACOSX_DEPLOYMENT_TARGET} 62 | NSHumanReadableCopyright 63 | Copyright © 2012-2016 Couchbase, Inc. All rights reserved. 64 | NSMainNibFile 65 | MainMenu 66 | NSPrincipalClass 67 | NSApplication 68 | 69 | 70 | -------------------------------------------------------------------------------- /Source/Couchbase Lite Viewer-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Couchbase Lite Viewer' target in the 'Couchbase Lite Viewer' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /Source/DBDocument.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBDocument.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 4/2/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** NSDocument subclass that manages a Couchbase Lite database file. */ 12 | @interface DBDocument : NSDocument 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Source/DBDocument.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBDocument.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 4/2/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "DBDocument.h" 10 | #import "DBWindowController.h" 11 | #import 12 | 13 | 14 | #define kPollInterval 2.0 15 | 16 | 17 | @implementation DBDocument 18 | { 19 | @private 20 | NSString* _path; 21 | CBLManager* _manager; 22 | CBLDatabase* _db; 23 | NSTimer* _pollTimer; 24 | uint64_t _lastSequence; 25 | } 26 | 27 | 28 | - (void) makeWindowControllers { 29 | DBWindowController* controller = [[DBWindowController alloc] initWithDatabase: _db 30 | atPath: _path]; 31 | [self addWindowController: controller]; 32 | } 33 | 34 | 35 | static BOOL returnErrorWithMessage(NSString* message, NSError **outError) { 36 | if (outError) { 37 | NSDictionary* userInfo = @{NSLocalizedFailureReasonErrorKey: message}; 38 | *outError = [NSError errorWithDomain: @"CBLViewer" code: -1 userInfo: userInfo]; 39 | } 40 | return NO; 41 | } 42 | 43 | 44 | - (BOOL)readFromURL:(NSURL *)absoluteURL 45 | ofType:(NSString *)typeName 46 | error:(NSError **)outError 47 | { 48 | NSLog(@"Opening %@", absoluteURL.path); 49 | _path = absoluteURL.path; 50 | NSString* extension = _path.pathExtension; 51 | if (![absoluteURL isFileURL] || ![@[@"cblite", @"cblite2"] containsObject: extension]) { 52 | return returnErrorWithMessage(@"This file format is not supported.", outError); 53 | } 54 | BOOL isNewFormat = [extension isEqualToString: @"cblite2"]; 55 | 56 | if (CBLVersion().doubleValue >= 1.2) { 57 | // If we have Couchbase Lite 1.2 we shouldn't open an old-format database because it'll be 58 | // upgraded to the new format, and that might make it unreadable in its host app: 59 | if (!isNewFormat) { 60 | return returnErrorWithMessage(@"This database is too old to be opened by this app. Its host app needs to be upgraded to 1.2 format first.", outError); 61 | } 62 | } else { 63 | if (isNewFormat) { 64 | return returnErrorWithMessage(@"This database is too new to be opened by this app. Use a Viewer that supports Couchbase Lite 1.2.", outError); 65 | } 66 | } 67 | 68 | CBLManagerOptions options = {.readOnly = false}; 69 | NSString* managerPath = _path.stringByDeletingLastPathComponent; 70 | NSError* error; 71 | _manager = [[CBLManager alloc] initWithDirectory: managerPath 72 | options: &options 73 | error: &error]; 74 | if (!_manager) { 75 | if (outError) *outError = error; 76 | return NO; 77 | } 78 | _db = [_manager databaseNamed: _path.lastPathComponent.stringByDeletingPathExtension 79 | error: &error]; 80 | if (!_db) { 81 | if (outError) *outError = error; 82 | return NO; 83 | } 84 | 85 | // Set up polling of lastSequence to detect external changes: 86 | _lastSequence = _db.lastSequenceNumber; 87 | NSLog(@"Database lastSequence = %llu", _lastSequence); 88 | [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(dbChanged:) 89 | name: kCBLDatabaseChangeNotification object: _db]; 90 | _pollTimer = [NSTimer scheduledTimerWithTimeInterval: kPollInterval 91 | target: self selector: @selector(checkForChanges:) 92 | userInfo: nil repeats: YES]; 93 | return YES; 94 | } 95 | 96 | 97 | - (void) close { 98 | [_pollTimer invalidate]; 99 | _pollTimer = nil; 100 | [[NSNotificationCenter defaultCenter] removeObserver: self 101 | name: kCBLDatabaseChangeNotification 102 | object: _db]; 103 | [_manager close]; 104 | [super close]; 105 | } 106 | 107 | 108 | - (void) checkForChanges: (NSTimer*)timer { 109 | uint64_t seq = _db.lastSequenceNumber; 110 | if (seq > _lastSequence) { 111 | NSLog(@"Detected external database change! (%llu)", seq); 112 | [[NSNotificationCenter defaultCenter] postNotificationName: kCBLDatabaseChangeNotification 113 | object: _db]; 114 | } 115 | } 116 | 117 | 118 | - (void) dbChanged: (NSNotification*)n { 119 | _lastSequence = _db.lastSequenceNumber; 120 | NSLog(@"Database changed (%llu)", _lastSequence); 121 | } 122 | 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /Source/DBWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBWindowController.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 4/2/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class CBLDatabase, DocEditor; 11 | 12 | 13 | @interface DBWindowController : NSWindowController 14 | 15 | - (id)initWithDatabase: (CBLDatabase*)db atPath: (NSString*)dbPath; 16 | 17 | - (IBAction) showDocRevisionTree:(id)sender; 18 | - (IBAction) newDocument: (id)sender; 19 | - (IBAction) deleteDocument: (id)sender; 20 | 21 | /** Either the QueryResultController or the RevTreeController */ 22 | @property (unsafe_unretained, readonly) id outlineController; 23 | 24 | - (BOOL) hasColumnForProperty: (NSArray*)propertyPath; 25 | - (void) addColumnForProperty: (NSArray*)propertyPath; 26 | - (void) removeColumnForProperty: (NSArray*)propertyPath; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Source/DBWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBWindowController.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 4/2/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "DBWindowController.h" 10 | #import "DocEditor.h" 11 | #import "QueryResultController.h" 12 | #import "RevTreeController.h" 13 | #import "JSONItem.h" 14 | 15 | 16 | @interface DBWindowController () 17 | { 18 | @private 19 | CBLDatabase* _db; 20 | NSString* _dbPath; 21 | NSTableColumn* _idCol, *_seqCol; 22 | 23 | IBOutlet QueryResultController* _queryController; 24 | IBOutlet RevTreeController* _revTreeController; 25 | IBOutlet DocEditor* _docEditor; 26 | IBOutlet NSOutlineView* _docsOutline; 27 | IBOutlet NSPathControl* _path; 28 | IBOutlet NSButton* _showDeletedCheckbox; 29 | } 30 | 31 | @end 32 | 33 | 34 | 35 | @implementation DBWindowController 36 | 37 | 38 | - (id)initWithDatabase: (CBLDatabase*)db atPath: (NSString*)dbPath 39 | { 40 | NSParameterAssert(db != nil); 41 | self = [super initWithWindowNibName: @"DBWindowController"]; 42 | if (self) { 43 | _db = db; 44 | _dbPath = dbPath.copy; 45 | } 46 | return self; 47 | } 48 | 49 | - (void) windowDidLoad { 50 | _docsOutline.target = self; 51 | _docsOutline.doubleAction = @selector(showDocRevisionTree:); 52 | 53 | _queryController.outline = _docsOutline; 54 | _queryController.query = [_db createAllDocumentsQuery]; 55 | 56 | _docEditor.database = _db; 57 | 58 | [self setPath: @[_db.name]]; 59 | 60 | // Set up the window title: 61 | self.window.title = _dbPath.lastPathComponent; 62 | self.window.representedFilename = _dbPath; 63 | } 64 | 65 | 66 | - (void) setPath: (NSArray*)path { 67 | NSString* urlStr = [@"foo:///" stringByAppendingString: [path componentsJoinedByString: @"/"]]; 68 | _path.URL = [NSURL URLWithString: urlStr]; 69 | NSArray* cells = _path.pathComponentCells; 70 | if (cells.count > 0) 71 | [cells[0] setImage: [NSImage imageNamed: @"database"]]; 72 | } 73 | 74 | 75 | #pragma mark - CUSTOM COLUMNS: 76 | 77 | 78 | static int jsonObjectRank(id a) { 79 | if (a == nil) 80 | return 0; 81 | if (a == (id)kCFNull) 82 | return 1; 83 | if (a == (id)kCFBooleanTrue || a == (id)kCFBooleanFalse) 84 | return 2; 85 | if ([a isKindOfClass: [NSNumber class]]) 86 | return 3; 87 | if ([a isKindOfClass: [NSString class]]) 88 | return 4; 89 | if ([a isKindOfClass: [NSArray class]]) 90 | return 5; 91 | return 6; 92 | } 93 | 94 | 95 | static NSComparisonResult jsonCompare(id a, id b) { 96 | if (a == b) 97 | return 0; 98 | int rankDelta = jsonObjectRank(a) - jsonObjectRank(b); 99 | if (rankDelta != 0) 100 | return rankDelta; 101 | else 102 | return [a compare: b]; 103 | } 104 | 105 | 106 | static void insertColumn(NSOutlineView* outline, NSTableColumn* col, NSUInteger index) { 107 | [outline addTableColumn: col]; 108 | NSUInteger curIndex = [outline.tableColumns indexOfObject: col]; 109 | if (curIndex != index) 110 | [outline moveColumn: curIndex toColumn: index]; 111 | } 112 | 113 | 114 | static NSString* identifierForProperty(NSArray* propertyPath) { 115 | return [@"." stringByAppendingString: [propertyPath componentsJoinedByString: @"."]]; 116 | } 117 | 118 | static NSString* asKeyPath(NSArray* path) { 119 | // We can convert a JSON path to a keypath unless it involves numeric indexes: 120 | NSMutableString* keyPath = [NSMutableString string]; 121 | for (id key in path) { 122 | if (![key isKindOfClass: [NSString class]]) 123 | return nil; 124 | if (keyPath.length > 0) 125 | [keyPath appendString: @"."]; 126 | [keyPath appendString: key]; 127 | } 128 | return keyPath; 129 | } 130 | 131 | static NSString* displayPath(NSArray* path) { 132 | NSMutableString* display = [NSMutableString string]; 133 | for (id key in path) { 134 | if ([key isKindOfClass: [NSString class]]) { 135 | if (display.length > 0) 136 | [display appendString: @"."]; 137 | [display appendString: key]; 138 | } else { 139 | [display appendFormat: @"[%@]", key]; 140 | } 141 | } 142 | return display; 143 | } 144 | 145 | 146 | - (BOOL) hasColumnForProperty: (NSArray*)propertyPath { 147 | NSString* identifier = identifierForProperty(propertyPath); 148 | return [_docsOutline tableColumnWithIdentifier: identifier] != nil; 149 | } 150 | 151 | 152 | - (void) addColumnForProperty: (NSArray*)propertyPath { 153 | NSString* identifier = identifierForProperty(propertyPath); 154 | if (![_docsOutline tableColumnWithIdentifier: identifier]) { 155 | NSTableColumn* col = [[NSTableColumn alloc] initWithIdentifier: identifier]; 156 | NSTableColumn* jsonCol = [_docsOutline tableColumnWithIdentifier: @"json"]; 157 | [col.dataCell setFont: [jsonCol.dataCell font]]; 158 | 159 | [col.headerCell setStringValue: displayPath(propertyPath)]; 160 | 161 | NSSortDescriptor* sort; 162 | NSString* keyPath = asKeyPath(propertyPath); 163 | if (keyPath) { 164 | NSString* sortKey = [@"documentProperties." stringByAppendingString: keyPath]; 165 | sort = [NSSortDescriptor sortDescriptorWithKey: sortKey 166 | ascending:YES 167 | comparator:^NSComparisonResult(id obj1, id obj2) { 168 | return jsonCompare(obj1, obj2); 169 | }]; 170 | } else { 171 | sort = [NSSortDescriptor sortDescriptorWithKey: @"documentProperties" ascending: YES 172 | comparator:^NSComparisonResult(NSDictionary* doc1, NSDictionary* doc2) { 173 | id val1 = [JSONItem itemAtPath: propertyPath inObject: doc1]; 174 | id val2 = [JSONItem itemAtPath: propertyPath inObject: doc2]; 175 | return jsonCompare(val1, val2); 176 | }]; 177 | } 178 | col.sortDescriptorPrototype = sort; 179 | [_queryController registerPath: propertyPath forColumn: col]; 180 | insertColumn(_docsOutline, col, _docsOutline.tableColumns.count - 1); 181 | } 182 | } 183 | 184 | 185 | - (void) removeColumnForProperty: (NSArray*)propertyPath { 186 | NSString* identifier = identifierForProperty(propertyPath); 187 | NSTableColumn* col = [_docsOutline tableColumnWithIdentifier: identifier]; 188 | if (col) { 189 | [_docsOutline removeTableColumn: col]; 190 | [_queryController unregisterColumn: col]; 191 | } 192 | } 193 | 194 | 195 | - (void) hideDocColumns { 196 | if (_seqCol) 197 | return; 198 | _docsOutline.outlineTableColumn = [_docsOutline tableColumnWithIdentifier: @"rev"]; 199 | _seqCol = [_docsOutline tableColumnWithIdentifier: @"seq"]; 200 | _idCol = [_docsOutline tableColumnWithIdentifier: @"id"]; 201 | [_docsOutline removeTableColumn: _seqCol]; 202 | [_docsOutline removeTableColumn: _idCol]; 203 | [_docsOutline sizeLastColumnToFit]; 204 | } 205 | 206 | - (void) showDocColumns { 207 | if (!_seqCol) 208 | return; 209 | insertColumn(_docsOutline, _seqCol, 0); 210 | insertColumn(_docsOutline, _idCol, 1); 211 | _docsOutline.outlineTableColumn = _seqCol; 212 | _seqCol = _idCol = nil; 213 | [_docsOutline sizeLastColumnToFit]; 214 | } 215 | 216 | 217 | #pragma mark - ACTIONS: 218 | 219 | 220 | - (id) outlineController { 221 | return _docsOutline.dataSource; 222 | } 223 | 224 | 225 | - (IBAction) showDocRevisionTree:(id)sender { 226 | if (_revTreeController.outline) 227 | return; 228 | NSArray* docs = _queryController.selectedDocuments; 229 | if (docs.count != 1) 230 | return; 231 | 232 | CBLDocument* doc = docs[0]; 233 | [self hideDocColumns]; 234 | [self willChangeValueForKey: @"outlineController"]; 235 | _revTreeController.document = doc; 236 | _queryController.outline = nil; 237 | _revTreeController.outline = _docsOutline; 238 | [self didChangeValueForKey: @"outlineController"]; 239 | [self setPath: @[_db.name, doc.documentID]]; 240 | } 241 | 242 | 243 | - (IBAction) hideDocRevisionTree: (id)sender { 244 | if (_queryController.outline) 245 | return; 246 | [self showDocColumns]; 247 | CBLDocument* doc = _revTreeController.document; 248 | [self willChangeValueForKey: @"outlineController"]; 249 | _revTreeController.document = nil; 250 | _revTreeController.outline = nil; 251 | _queryController.outline = _docsOutline; 252 | [_queryController selectDocument: doc]; 253 | [self didChangeValueForKey: @"outlineController"]; 254 | [self setPath: @[_db.name]]; 255 | } 256 | 257 | 258 | - (IBAction) pathClicked: (id)sender { 259 | NSUInteger index = [_path.pathComponentCells indexOfObjectIdenticalTo: _path.clickedPathComponentCell]; 260 | if (index == 0) 261 | [self hideDocRevisionTree: sender]; 262 | } 263 | 264 | 265 | - (IBAction) newDocument: (id)sender { 266 | [_queryController newDocument: sender]; 267 | } 268 | 269 | 270 | - (IBAction) deleteDocument: (id)sender { 271 | [_queryController deleteDocument: sender]; 272 | } 273 | 274 | 275 | - (void) keyDown: (NSEvent*)ev { 276 | if (ev.type == NSKeyDown) { 277 | NSString* keys = ev.characters; 278 | if (keys.length == 1) { 279 | unichar key = [keys characterAtIndex: 0]; 280 | if (key == 0x7F || key == NSDeleteCharFunctionKey) { 281 | // Delete key -- delete from focused table view: 282 | NSResponder* responder = [self.window firstResponder]; 283 | if (responder == _docsOutline) { 284 | [self deleteDocument: self]; 285 | return; 286 | } else if (responder == _docEditor.tableView) { 287 | [_docEditor removeProperty: self]; 288 | return; 289 | } 290 | } 291 | } 292 | } 293 | NSBeep(); 294 | } 295 | 296 | 297 | - (IBAction) copy:(id)sender { 298 | id focus = self.window.firstResponder; 299 | if ([focus isKindOfClass: [NSTableView class]]) { 300 | focus = [focus delegate]; 301 | if ([focus respondsToSelector: @selector(copy:)]) { 302 | [focus copy: sender]; 303 | return; 304 | } 305 | } 306 | NSBeep(); 307 | } 308 | 309 | 310 | @end 311 | -------------------------------------------------------------------------------- /Source/DBWindowController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 119 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 226 | 240 | 254 | 268 | 279 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | -------------------------------------------------------------------------------- /Source/DocEditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // DocEditor.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 5/4/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class DBWindowController, JSONItem; 11 | 12 | 13 | @interface DocEditor : NSObject 14 | 15 | @property (weak) CBLDatabase* database; 16 | 17 | @property (weak) CBLSavedRevision* revision; 18 | 19 | @property BOOL readOnly; 20 | 21 | - (void) editNewDocument; 22 | 23 | - (IBAction) addProperty: (id)sender; 24 | - (IBAction) removeProperty: (id)sender; 25 | 26 | - (IBAction) addColumnForSelectedProperty:(id)sender; 27 | 28 | @property (weak, readonly) NSOutlineView* tableView; 29 | 30 | @property (copy) JSONItem* selectedProperty; 31 | 32 | - (IBAction) saveDocument: (id)sender; 33 | - (IBAction) revertDocumentToSaved:(id)sender; 34 | 35 | - (BOOL) saveDocument; 36 | - (IBAction) cancelOperation: (id)sender; 37 | - (IBAction) copy:(id)sender; 38 | 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Source/DocEditor.m: -------------------------------------------------------------------------------- 1 | // 2 | // DocEditor.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 5/4/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "DocEditor.h" 10 | #import "DBWindowController.h" 11 | #import "JSONFormatter.h" 12 | #import "JSONItem.h" 13 | #import "JSONKeyFormatter.h" 14 | #import 15 | 16 | 17 | @implementation DocEditor 18 | { 19 | CBLDatabase* __weak _db; 20 | CBLSavedRevision* _revision; 21 | BOOL _readOnly; 22 | BOOL _untitled; 23 | JSONItem* _root; 24 | int _editErrorCount; 25 | BOOL _cancelingEdit; 26 | 27 | IBOutlet DBWindowController* _dbWindowController; 28 | IBOutlet NSOutlineView* __weak _outline; 29 | IBOutlet NSButton *_addPropertyButton, *_removePropertyButton; 30 | IBOutlet NSButton *_saveButton, *_revertButton; 31 | IBOutlet JSONKeyFormatter *_keyFormatter; 32 | } 33 | 34 | 35 | @synthesize database=_db, tableView=_outline, readOnly=_readOnly; 36 | 37 | 38 | - (void) awakeFromNib { 39 | NSLayoutConstraint* c = [NSLayoutConstraint constraintWithItem: _addPropertyButton 40 | attribute: NSLayoutAttributeLeft 41 | relatedBy: NSLayoutRelationEqual 42 | toItem: _outline 43 | attribute: NSLayoutAttributeLeft 44 | multiplier: 1.0 constant: 0]; 45 | [_outline.window.contentView addConstraint: c]; 46 | } 47 | 48 | 49 | - (CBLSavedRevision*) revision { 50 | return _revision; 51 | } 52 | 53 | - (void) setRevision: (CBLSavedRevision*)rev { 54 | if ((rev != _revision && ![rev isEqual: _revision]) || _untitled) { 55 | _revision = rev; 56 | _untitled = NO; 57 | [self revertDocumentToSaved: self]; 58 | } 59 | } 60 | 61 | 62 | - (void) editNewDocument { 63 | if (!_untitled) { 64 | _revision = nil; 65 | _untitled = YES; 66 | [self revertDocumentToSaved: self]; 67 | } 68 | } 69 | 70 | 71 | - (void) rebuildTable { 72 | if (_revision) { 73 | _root = [[JSONItem alloc] initWithValue: _revision.properties]; 74 | } else if (_untitled) { 75 | NSString* docID = [NSUUID UUID].UUIDString; 76 | _root = [[JSONItem alloc] initWithValue: @{@"_id": docID}]; 77 | } else { 78 | _root = nil; 79 | } 80 | [_outline reloadData]; 81 | } 82 | 83 | - (void) reloadItem: (JSONItem*)item { 84 | if (item == _root) 85 | item = nil; 86 | [_outline reloadItem: item reloadChildren: YES]; 87 | } 88 | 89 | - (void) redrawItem: (JSONItem*)item { 90 | NSInteger row = [_outline rowForItem: item]; 91 | if (row >= 0) 92 | [_outline setNeedsDisplayInRect: [_outline rectOfRow: row]]; 93 | } 94 | 95 | 96 | - (JSONItem*) selectedProperty { 97 | NSInteger row = _outline.selectedRow; 98 | return row >= 0 ? [_outline itemAtRow: row] : nil; 99 | } 100 | 101 | 102 | - (void) setSelectedProperty: (JSONItem*)property { 103 | NSUInteger row = [_outline rowForItem: property]; 104 | NSIndexSet* indexes = nil; 105 | if (row != NSNotFound) 106 | indexes = [NSIndexSet indexSetWithIndex: row]; 107 | else 108 | indexes = [NSIndexSet indexSet]; 109 | [_outline selectRowIndexes: indexes byExtendingSelection: NO]; 110 | [self enablePropertyButtons]; 111 | } 112 | 113 | 114 | - (JSONItem*) selectedOrClickedProperty { 115 | NSInteger row = _outline.clickedRow; 116 | if (row < 0) 117 | row = _outline.selectedRow; 118 | return row >= 0 ? [_outline itemAtRow: row] : nil; 119 | } 120 | 121 | 122 | - (BOOL) saveDocument { 123 | NSDictionary* properties = _root.value; 124 | if (!_readOnly && _root && ![properties isEqual: _revision.properties]) { 125 | CBLDocument* doc; 126 | NSError* error; 127 | if (_revision) 128 | doc = _revision.document; 129 | else 130 | doc = _db[properties[@"_id"]]; 131 | if (![doc putProperties: _root.value error: &error]) { 132 | [_outline presentError: error]; 133 | return NO; 134 | } 135 | self.revision = doc.currentRevision; 136 | } 137 | _saveButton.hidden = _revertButton.hidden = YES; 138 | return YES; 139 | } 140 | 141 | 142 | - (IBAction) saveDocument: (id)sender { 143 | [self saveDocument]; 144 | } 145 | 146 | 147 | - (IBAction) revertDocumentToSaved:(id)sender { 148 | NSArray* selectedPath = self.selectedProperty.path; 149 | [self rebuildTable]; 150 | self.selectedProperty = [_root itemAtPath: selectedPath]; 151 | _saveButton.hidden = _revertButton.hidden = !_untitled; 152 | } 153 | 154 | 155 | #pragma mark - ACTIONS: 156 | 157 | 158 | - (IBAction) addProperty: (id)sender { 159 | JSONItem* sibling = self.selectedProperty; 160 | JSONItem* parent; 161 | if (sibling == nil) { 162 | parent = _root; 163 | } else if ([_outline isItemExpanded: sibling]) { 164 | parent = sibling; 165 | sibling = parent.children.firstObject; 166 | } else { 167 | parent = sibling.parent; 168 | } 169 | 170 | JSONItem *newItem = [parent createChildBefore: sibling]; 171 | if (!newItem) { 172 | NSBeep(); 173 | return; 174 | } 175 | self.selectedProperty = newItem; 176 | [self reloadItem: parent]; 177 | [_outline editColumn: (parent.isArray ? 1 : 0) 178 | row: [_outline rowForItem: newItem] 179 | withEvent: nil 180 | select: YES]; 181 | _saveButton.hidden = _revertButton.hidden = NO; 182 | } 183 | 184 | 185 | - (IBAction) removeProperty: (id)sender { 186 | JSONItem* prop = self.selectedOrClickedProperty; 187 | if (_readOnly || !prop || prop.isSpecial) { 188 | NSBeep(); 189 | return; 190 | } 191 | JSONItem* parent = prop.parent; 192 | [parent removeChild: prop]; 193 | [self reloadItem: parent]; 194 | _saveButton.hidden = _revertButton.hidden = NO; 195 | } 196 | 197 | 198 | - (IBAction) addColumnForSelectedProperty:(id)sender { 199 | NSArray* property = self.selectedOrClickedProperty.path; 200 | if (!property) 201 | return; 202 | if ([_dbWindowController hasColumnForProperty: property]) 203 | [_dbWindowController removeColumnForProperty: property]; 204 | else 205 | [_dbWindowController addColumnForProperty: property]; 206 | [self enablePropertyButtons]; 207 | } 208 | 209 | 210 | - (IBAction) cancelOperation: (id)sender { 211 | if (_outline.editedRow >= 0) { 212 | _cancelingEdit = YES; 213 | @try { 214 | [_outline editColumn: -1 row: -1 withEvent: nil select: NO]; 215 | } @finally { 216 | _cancelingEdit = NO; 217 | } 218 | [_outline reloadData]; 219 | } 220 | } 221 | 222 | 223 | - (IBAction) copy: (id)sender { 224 | JSONItem* prop = self.selectedProperty; 225 | if (!prop) { 226 | NSBeep(); 227 | return; 228 | } 229 | id value = prop.value; 230 | NSString* json = [JSONFormatter stringForObjectValue: value]; 231 | 232 | NSPasteboard* pb = [NSPasteboard generalPasteboard]; 233 | [pb clearContents]; 234 | [pb setString: json forType: NSStringPboardType]; 235 | } 236 | 237 | 238 | - (BOOL) validateUserInterfaceItem: (id )item { 239 | JSONItem* jsonItem = self.selectedOrClickedProperty; 240 | SEL action = [item action]; 241 | if (action == @selector(addColumnForSelectedProperty:)) { 242 | NSArray* property = jsonItem.path; 243 | NSString* title; 244 | if (property && [_dbWindowController hasColumnForProperty: property]) 245 | title = @"Remove Column"; 246 | else 247 | title = @"Add Column"; 248 | [(id)item setTitle: title]; 249 | return (property != nil); 250 | } else if (action == @selector(removeProperty:)) { 251 | return (!_readOnly && jsonItem != nil && !jsonItem.isSpecial); 252 | } 253 | return YES; 254 | } 255 | 256 | - (void) enablePropertyButtons { 257 | JSONItem* selectedProperty = self.selectedProperty; 258 | BOOL canInsert = !_readOnly && (_revision != nil || _untitled); 259 | BOOL canRemove = !_readOnly && selectedProperty && !selectedProperty.isSpecial; 260 | _addPropertyButton.enabled = canInsert; 261 | _removePropertyButton.enabled = canRemove; 262 | } 263 | 264 | 265 | #pragma mark - OUTLINE VIEW DATA SOURCE / DELEGATE: 266 | 267 | 268 | - (id) outlineView: (NSOutlineView *)outlineView 269 | objectValueForTableColumn: (NSTableColumn *)tableColumn 270 | byItem: (JSONItem*)item 271 | { 272 | if ([tableColumn.identifier isEqualToString: @"key"]) { 273 | return item.key; 274 | } else { 275 | if ([outlineView isItemExpanded: item]) 276 | return nil; 277 | else 278 | return item.value; 279 | } 280 | } 281 | 282 | - (BOOL) outlineView: (NSOutlineView *)outlineView isItemExpandable: (JSONItem*)item { 283 | return item == nil || item.children != nil; 284 | } 285 | 286 | - (NSInteger) outlineView: (NSOutlineView *)outlineView numberOfChildrenOfItem: (JSONItem*)item { 287 | item = item ?: _root; 288 | return item.children.count; 289 | } 290 | 291 | - (id) outlineView: (NSOutlineView *)outlineView child: (NSInteger)index ofItem: (JSONItem*)item { 292 | item = item ?: _root; 293 | return item.children[index]; 294 | } 295 | 296 | 297 | - (void)outlineView: (NSOutlineView *)outlineView 298 | willDisplayCell: (NSTextFieldCell*)cell 299 | forTableColumn: (NSTableColumn *)tableColumn 300 | item: (JSONItem*)item 301 | { 302 | BOOL isKeyColumn = [tableColumn.identifier isEqualToString: @"key"]; 303 | NSColor* color; 304 | if (!item.isSpecial || (_untitled && !isKeyColumn && [item.key isEqual: @"_id"])) 305 | color = [NSColor controlTextColor]; 306 | else 307 | color = [NSColor disabledControlTextColor]; 308 | [cell setTextColor: color]; 309 | } 310 | 311 | 312 | 313 | - (BOOL)outlineView:(NSOutlineView *)outlineView 314 | shouldEditTableColumn:(NSTableColumn *)tableColumn 315 | item: (JSONItem*)item 316 | { 317 | BOOL isKeyColumn = [tableColumn.identifier isEqualToString: @"key"]; 318 | if (_readOnly) 319 | return NO; 320 | if (isKeyColumn && item.parent.isArray) 321 | return NO; 322 | if (!isKeyColumn && [outlineView isItemExpanded: item]) 323 | return NO; 324 | if (item.isSpecial) { 325 | if (isKeyColumn) 326 | return NO; 327 | // You can edit the value of the _id property, in an untitled document: 328 | if (!(_untitled && [item.key isEqualToString: @"_id"])) 329 | return NO; 330 | } 331 | if (isKeyColumn) 332 | _keyFormatter.item = item; 333 | _editErrorCount = 0; 334 | return YES; 335 | } 336 | 337 | 338 | - (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item { 339 | // Don't expand a (dict/array) item whose JSON is being edited inline: 340 | return [outlineView rowForItem: item] != [outlineView editedRow]; 341 | } 342 | 343 | 344 | - (void) outlineView: (NSOutlineView*)outlineView 345 | setObjectValue: (id)newCellValue 346 | forTableColumn: (NSTableColumn *)tableColumn 347 | byItem: (JSONItem*)item 348 | { 349 | if (_cancelingEdit) 350 | return; 351 | if (item.isSpecial && !_untitled) 352 | return; 353 | 354 | if (newCellValue == nil || [newCellValue isEqual: @""]) { 355 | // User entered empty key or value: delete property 356 | JSONItem* parent = item.parent; 357 | [parent removeChild: item]; 358 | [self reloadItem: parent]; 359 | } else if ([tableColumn.identifier isEqualToString: @"key"]) { 360 | item.key = newCellValue; 361 | //TODO: Re-sort item 362 | } else { 363 | item.value = newCellValue; 364 | [self reloadItem: item]; // expandability may have changed 365 | } 366 | _saveButton.hidden = _revertButton.hidden = NO; 367 | } 368 | 369 | 370 | - (BOOL) control: (NSControl*)control 371 | didFailToFormatString: (NSString*)string 372 | errorDescription: (NSString*)errorMessage 373 | { 374 | if (_cancelingEdit) 375 | return YES; 376 | NSBeep(); 377 | if (++_editErrorCount >= 2) { 378 | NSAlert* alert = [NSAlert new]; 379 | alert.messageText = @"Invalid JSON in property value"; 380 | alert.informativeText = errorMessage; 381 | [alert addButtonWithTitle: @"Continue"]; 382 | [alert addButtonWithTitle: @"Cancel"]; 383 | [alert beginSheetModalForWindow: control.window 384 | completionHandler:^(NSModalResponse returnCode) { 385 | if (returnCode == NSAlertSecondButtonReturn) 386 | [self cancelOperation: self]; 387 | }]; 388 | } 389 | return NO; 390 | } 391 | 392 | 393 | - (void)outlineViewSelectionDidChange:(NSNotification *)notification { 394 | [self enablePropertyButtons]; 395 | } 396 | 397 | 398 | - (BOOL)control:(NSControl *)control 399 | textView:(NSTextView *)textView 400 | doCommandBySelector:(SEL)command 401 | { 402 | //NSLog(@"command: %@", NSStringFromSelector(command)); 403 | if (command == @selector(cancelOperation:)) { // Esc key 404 | [self cancelOperation: self]; 405 | return YES; 406 | } 407 | return NO; 408 | } 409 | 410 | 411 | @end 412 | -------------------------------------------------------------------------------- /Source/DocHistory.h: -------------------------------------------------------------------------------- 1 | // 2 | // DocHistory.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 8/29/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NSTreeNode* GetDocRevisionTree(CBLDocument* doc); 12 | 13 | NSSet* GetLeafNodes(NSTreeNode* tree); 14 | 15 | NSTreeNode* CopyTree(NSTreeNode* root); 16 | 17 | void FlattenTree(NSTreeNode* root); 18 | 19 | NSTreeNode* TreeWithoutDeletedBranches(NSTreeNode* root); 20 | 21 | NSString* DumpDocRevisionTree(NSTreeNode* root); 22 | -------------------------------------------------------------------------------- /Source/DocHistory.m: -------------------------------------------------------------------------------- 1 | // 2 | // DocHistory.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 8/29/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "DocHistory.h" 10 | #import 11 | 12 | 13 | static NSTreeNode* SortRevisionTree(NSTreeNode* tree) { 14 | [tree.mutableChildNodes sortUsingComparator: ^NSComparisonResult(id obj1, id obj2) { 15 | CBLSavedRevision* rev1 = [obj1 representedObject]; 16 | CBLSavedRevision* rev2 = [obj2 representedObject]; 17 | // Plain string compare is OK because sibling revs must start with the same gen #. 18 | // Comparing backwards because we want descending rev IDs, i.e. winner first. 19 | return [rev2.revisionID compare: rev1.revisionID]; 20 | }]; 21 | for (NSTreeNode* child in tree.childNodes) 22 | SortRevisionTree(child); 23 | return tree; 24 | } 25 | 26 | 27 | NSTreeNode* GetDocRevisionTree(CBLDocument* doc) { 28 | NSError* error; 29 | NSArray* leaves = [doc getLeafRevisions: &error]; 30 | if (!leaves) 31 | return nil; 32 | NSTreeNode* root = [NSTreeNode treeNodeWithRepresentedObject: nil]; 33 | NSMutableDictionary* nodes = [NSMutableDictionary dictionary]; 34 | for (CBLSavedRevision* leaf in leaves) { 35 | // Get history of this leaf/conflict: 36 | NSArray* history = [leaf getRevisionHistory: &error]; 37 | if (!history) 38 | return nil; 39 | NSTreeNode* node = nil; 40 | NSTreeNode* child = nil; 41 | for (NSInteger i = (NSInteger)history.count - 1; i >= 0; i--) { 42 | // Create a rev and a tree node: 43 | CBLSavedRevision* rev = history[i]; 44 | NSString* revID = rev.revisionID; 45 | node = nodes[revID]; 46 | BOOL exists = (node != nil); 47 | if (!exists) { 48 | node = [NSTreeNode treeNodeWithRepresentedObject: rev]; 49 | nodes[revID] = node; 50 | } 51 | // Add to the tree: 52 | if (child) 53 | [node.mutableChildNodes addObject: child]; 54 | child = node; 55 | if (exists) { 56 | node = nil; 57 | break; 58 | } 59 | } 60 | if (node) 61 | [root.mutableChildNodes addObject: node]; 62 | } 63 | SortRevisionTree(root); 64 | return root; 65 | } 66 | 67 | 68 | static void addLeafNodes(NSTreeNode* node, NSMutableSet* leaves) { 69 | if (node.isLeaf) 70 | [leaves addObject: node]; 71 | else { 72 | for (NSTreeNode* child in node.childNodes) 73 | addLeafNodes(child, leaves); 74 | } 75 | } 76 | 77 | 78 | NSSet* GetLeafNodes(NSTreeNode* tree) { 79 | NSMutableSet* leaves = [NSMutableSet set]; 80 | addLeafNodes(tree, leaves); 81 | return leaves; 82 | } 83 | 84 | 85 | NSTreeNode* CopyTree(NSTreeNode* root) { 86 | if (!root) 87 | return nil; 88 | NSTreeNode* copiedRoot = [NSTreeNode treeNodeWithRepresentedObject: root.representedObject]; 89 | for (NSTreeNode* child in root.childNodes) 90 | [copiedRoot.mutableChildNodes addObject: CopyTree(child)]; 91 | return copiedRoot; 92 | } 93 | 94 | 95 | void FlattenTree(NSTreeNode* root) { 96 | if (!root) 97 | return; 98 | // If this node has one child, make its linear decendent chain into direct children: 99 | if (root.childNodes.count == 1) { 100 | NSTreeNode* child = root; 101 | while (!child.isLeaf) { 102 | NSTreeNode* parent = child; 103 | child = child.childNodes[0]; 104 | [parent.mutableChildNodes removeObject: child]; 105 | [root.mutableChildNodes addObject: child]; 106 | } 107 | } 108 | // Now recurse: 109 | for (NSTreeNode* child in root.childNodes) 110 | FlattenTree(child); 111 | } 112 | 113 | 114 | NSTreeNode* TreeWithoutDeletedBranches(NSTreeNode* root) { 115 | if (!root) 116 | return nil; 117 | CBLSavedRevision* rev = root.representedObject; 118 | if (root.isLeaf) { 119 | if (rev.isDeletion) 120 | return nil; 121 | return [NSTreeNode treeNodeWithRepresentedObject: rev]; 122 | } else { 123 | NSMutableArray* children = [NSMutableArray array]; 124 | for (NSTreeNode* child in root.childNodes) { 125 | NSTreeNode* prunedChild = TreeWithoutDeletedBranches(child); 126 | if (prunedChild) 127 | [children addObject: prunedChild]; 128 | } 129 | if (children.count == 0) 130 | return nil; 131 | root = [NSTreeNode treeNodeWithRepresentedObject: rev]; 132 | [root.mutableChildNodes setArray: children]; 133 | return root; 134 | } 135 | } 136 | 137 | 138 | static void dumpTree(NSTreeNode* node, int indent, NSMutableString* output) { 139 | for (int i = 0; i < indent; ++i) 140 | [output appendString: @" "]; 141 | CBLSavedRevision* rev = node.representedObject; 142 | [output appendFormat: @"%@%@\n", rev.revisionID, (rev.isDeletion ? @" DEL" : @"")]; 143 | for (NSTreeNode* child in node.childNodes) 144 | dumpTree(child, indent+1, output); 145 | } 146 | 147 | 148 | NSString* DumpDocRevisionTree(NSTreeNode* root) { 149 | NSMutableString* output = [NSMutableString string]; 150 | dumpTree(root, 0, output); 151 | return output; 152 | } 153 | -------------------------------------------------------------------------------- /Source/JSONFormatter.h: -------------------------------------------------------------------------------- 1 | // 2 | // JSONFormatter.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 5/4/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface JSONFormatter : NSFormatter 12 | 13 | + (NSString*) stringForObjectValue: (id)obj; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Source/JSONFormatter.m: -------------------------------------------------------------------------------- 1 | // 2 | // JSONFormatter.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 5/4/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "JSONFormatter.h" 10 | 11 | @implementation JSONFormatter 12 | 13 | 14 | + (NSString*) stringForObjectValue: (id)obj { 15 | if (obj == nil) 16 | return @""; 17 | NSArray* wrapped = @[obj]; // in case obj is a fragment 18 | NSData* data = [NSJSONSerialization dataWithJSONObject: wrapped options: 0 error: nil]; 19 | data = [data subdataWithRange: NSMakeRange(1, data.length - 2)]; 20 | return [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; 21 | } 22 | 23 | 24 | - (NSString *)stringForObjectValue: (id)obj { 25 | // For display purposes, remove the escaping before slashes that NSJSONSerialization puts in. 26 | // I'm not 100% sure this is always safe, so I'm not doing it for the string being edited. 27 | NSString* json = [[self class] stringForObjectValue: obj]; 28 | return [json stringByReplacingOccurrencesOfString: @"\\/" withString: @"/"]; 29 | } 30 | 31 | - (nullable NSString *)editingStringForObjectValue: (id)obj { 32 | return [[self class] stringForObjectValue: obj]; 33 | } 34 | 35 | 36 | - (BOOL)getObjectValue:(out id *)obj 37 | forString:(NSString *)string 38 | errorDescription:(out NSString **)errorMessage 39 | { 40 | if (string.length == 0) { 41 | // Empty string becomes a true nil value (as opposed to NSNull) 42 | *obj = nil; 43 | return YES; 44 | } 45 | NSData* data = [string dataUsingEncoding: NSUTF8StringEncoding]; 46 | NSError* error; 47 | *obj = [NSJSONSerialization JSONObjectWithData: data 48 | options: NSJSONReadingAllowFragments 49 | error: &error]; 50 | if (*obj) { 51 | return YES; 52 | } else { 53 | if (errorMessage) 54 | *errorMessage = error.userInfo[@"NSDebugDescription"]; 55 | return NO; 56 | } 57 | } 58 | 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Source/JSONItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // JSONItem.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 10/20/15. 6 | // Copyright © 2015 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface JSONItem : NSObject 12 | 13 | - (instancetype) initWithValue: (id)value; 14 | 15 | @property (copy, nonatomic) id value; 16 | @property (copy, nonatomic) id key; 17 | 18 | @property (readonly, nonatomic) NSArray* children; 19 | @property (readonly, nonatomic, weak) JSONItem* parent; 20 | 21 | @property (readonly) BOOL isRoot; 22 | @property (readonly) BOOL isSpecial; 23 | @property (readonly) BOOL isArray; 24 | @property (readonly) BOOL isDictionary; 25 | @property (readonly) BOOL isKeyEditable; 26 | 27 | - (JSONItem*) objectAtIndexedSubscript: (NSUInteger)index; 28 | - (JSONItem*) objectForKeyedSubscript: (id)key; 29 | 30 | @property (readonly) NSArray* path; 31 | - (JSONItem*) itemAtPath: (NSArray*)path; 32 | 33 | + (id) itemAtPath: (NSArray*)path inObject: (id)value; 34 | 35 | - (JSONItem*) createChildBefore: (JSONItem*)sibling; 36 | - (BOOL) removeChild: (JSONItem*)child; 37 | 38 | @end 39 | 40 | 41 | -------------------------------------------------------------------------------- /Source/JSONItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // JSONItem.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 10/20/15. 6 | // Copyright © 2015 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "JSONItem.h" 10 | 11 | @implementation JSONItem 12 | 13 | @synthesize value=_value, key=_key, children=_children, parent=_parent; 14 | 15 | static BOOL isSpecialProperty(NSString* key) { 16 | return [key hasPrefix: @"_"]; 17 | } 18 | 19 | - (instancetype) initWithValue: (id)value { 20 | return [self initWithValue: value key: nil parent: nil]; 21 | } 22 | 23 | - (instancetype) initWithValue: (id)value key: (id)key parent: (JSONItem*)parent { 24 | self = [super init]; 25 | if (self) { 26 | _value = value; 27 | _key = key; 28 | _parent = parent; 29 | [self rebuildChildren]; 30 | } 31 | return self; 32 | } 33 | 34 | 35 | - (void) rebuildChildren { 36 | NSMutableArray* children = nil; 37 | if ([_value isKindOfClass: [NSArray class]]) { 38 | children = [NSMutableArray arrayWithCapacity: [_value count]]; 39 | NSUInteger index = 0; 40 | for (NSObject *child in _value) 41 | [children addObject: [[JSONItem alloc] initWithValue: child 42 | key: @(index++) 43 | parent: self]]; 44 | } else if ([_value isKindOfClass: [NSDictionary class]]) { 45 | children = [NSMutableArray arrayWithCapacity: [_value count]]; 46 | NSMutableArray* keys = [[_value allKeys] mutableCopy]; 47 | if (_key) { 48 | [keys sortUsingSelector: @selector(caseInsensitiveCompare:)]; 49 | } else { 50 | // top level 51 | [keys sortUsingComparator: ^NSComparisonResult(NSString* key1, NSString* key2) { 52 | int n = (isSpecialProperty(key2) != 0) - (isSpecialProperty(key1) != 0); 53 | return n ?: [key1 caseInsensitiveCompare: key2]; 54 | }]; 55 | } 56 | for (NSObject *key in keys) 57 | [children addObject: [[JSONItem alloc] initWithValue: [_value objectForKey: key] 58 | key: key 59 | parent: self]]; 60 | } 61 | _children = children; 62 | } 63 | 64 | 65 | - (JSONItem*) objectAtIndexedSubscript: (NSUInteger)index { 66 | return _children[index]; 67 | } 68 | 69 | - (JSONItem*) objectForKeyedSubscript: (id)key { 70 | if ([key isKindOfClass: [NSNumber class]]) { 71 | NSInteger index = [key longLongValue]; 72 | if (self.isArray && index >= 0 && index < [_value count]) 73 | return [_children objectAtIndex: index]; 74 | } else if ([key isKindOfClass: [NSString class]]) { 75 | if (self.isDictionary) { 76 | for (JSONItem* child in _children) 77 | if ([child.key isEqual: key]) 78 | return child; 79 | } 80 | } 81 | return nil; 82 | } 83 | 84 | - (BOOL) isRoot { 85 | return _key == nil; 86 | } 87 | 88 | - (BOOL) isSpecial { 89 | return _parent.isSpecial 90 | || (_parent.isRoot && [_key isKindOfClass: [NSString class]] && [_key hasPrefix: @"_"]); 91 | } 92 | 93 | - (BOOL) isArray { 94 | return [_value isKindOfClass: [NSArray class]]; 95 | } 96 | 97 | - (BOOL) isDictionary { 98 | return [_value isKindOfClass: [NSDictionary class]]; 99 | } 100 | 101 | 102 | - (JSONItem*) createChildBefore: (JSONItem*)sibling { 103 | if (!self.isArray && !self.isDictionary) 104 | return nil; 105 | NSUInteger index = sibling ? [_children indexOfObjectIdenticalTo: sibling] : _children.count; 106 | if (index == NSNotFound) 107 | return nil; 108 | 109 | JSONItem* newChild = [[JSONItem alloc] initWithValue: nil 110 | key: (self.isArray ? @(index) : @"") 111 | parent: self]; 112 | 113 | NSMutableArray* children = [_children mutableCopy]; 114 | [children insertObject: newChild atIndex: index]; 115 | _children = children; 116 | [self fixupChildren: children from: index]; 117 | [self childChanged]; 118 | return newChild; 119 | } 120 | 121 | 122 | - (BOOL) removeChild: (JSONItem*)child { 123 | NSUInteger index = [_children indexOfObjectIdenticalTo: child]; 124 | if (index == NSNotFound) 125 | return NO; 126 | NSMutableArray* children = _children.mutableCopy; 127 | [children removeObjectAtIndex: index]; 128 | _children = children; 129 | 130 | [self fixupChildren: children from: index]; 131 | [self childChanged]; 132 | return YES; 133 | } 134 | 135 | - (void) fixupChildren: (NSMutableArray*)children from: (NSUInteger)index { 136 | if (self.isArray) { 137 | // Renumber succeeding children of an array: 138 | for (; index < children.count; index++) 139 | children[index]->_key = @(index); 140 | } 141 | } 142 | 143 | 144 | - (BOOL) isKeyEditable { 145 | return [_key isKindOfClass: [NSString class]] && !self.isSpecial; 146 | } 147 | 148 | 149 | - (void) setValue: (id)value { 150 | if (value == _value || [value isEqual: _value]) 151 | return; 152 | _value = [value copy]; 153 | [self rebuildChildren]; 154 | [_parent childChanged]; 155 | } 156 | 157 | - (void) setKey: (id)key { 158 | if (key == _key || [key isEqual: _key]) 159 | return; 160 | NSAssert(self.isKeyEditable, @"Can't change key of %@", self); 161 | _key = [key copy]; 162 | [_parent childChanged]; 163 | //TODO: Re-sort parent's children 164 | } 165 | 166 | - (void) childChanged { 167 | _value = [self computeValue]; 168 | [_parent childChanged]; 169 | } 170 | 171 | - (id) computeValue { 172 | if (self.isArray) { 173 | NSMutableArray* v = [NSMutableArray arrayWithCapacity: _children.count]; 174 | for (JSONItem* child in _children) { 175 | if (child.value) 176 | [v addObject: child.value]; 177 | } 178 | return v; 179 | } else if (self.isDictionary) { 180 | NSMutableDictionary* v = [NSMutableDictionary dictionaryWithCapacity: _children.count]; 181 | for (JSONItem* child in _children) { 182 | if (child.value) 183 | [v setObject: child.value forKey: child.key]; 184 | } 185 | return v; 186 | } else { 187 | return _value; 188 | } 189 | } 190 | 191 | 192 | - (NSArray*) path { 193 | NSMutableArray* path = [NSMutableArray array]; 194 | for (JSONItem* item = self; !item.isRoot; item=item.parent) 195 | [path insertObject: item.key atIndex: 0]; 196 | return path; 197 | } 198 | 199 | 200 | - (JSONItem*) itemAtPath: (NSArray*)path { 201 | JSONItem* item = self; 202 | for (id p in path) { 203 | item = item[p]; 204 | if (!item) 205 | break; 206 | } 207 | return item; 208 | } 209 | 210 | 211 | + (id) itemAtPath: (NSArray*)path inObject: (id)value { 212 | for (id key in path) { 213 | if ([key isKindOfClass: [NSNumber class]]) { 214 | NSInteger index = [key longLongValue]; 215 | if (![value isKindOfClass: [NSArray class]]) 216 | return nil; 217 | NSUInteger count = [value count]; 218 | if (index < 0) 219 | index += count; 220 | if (index < 0 || index >= count) 221 | return nil; 222 | value = [value objectAtIndex: index]; 223 | } else { 224 | if (![value isKindOfClass: [NSDictionary class]]) 225 | return nil; 226 | value = [value objectForKey: key]; 227 | if (!value) 228 | return nil; 229 | } 230 | } 231 | return value; 232 | } 233 | 234 | 235 | @end 236 | -------------------------------------------------------------------------------- /Source/JSONKeyFormatter.h: -------------------------------------------------------------------------------- 1 | // 2 | // JSONKeyFormatter.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 10/21/15. 6 | // Copyright © 2015 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | @class JSONItem; 11 | 12 | @interface JSONKeyFormatter : NSFormatter 13 | 14 | @property JSONItem* item; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Source/JSONKeyFormatter.m: -------------------------------------------------------------------------------- 1 | // 2 | // JSONKeyFormatter.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 10/21/15. 6 | // Copyright © 2015 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "JSONKeyFormatter.h" 10 | #import "JSONItem.h" 11 | 12 | 13 | @implementation JSONKeyFormatter 14 | 15 | @synthesize item=_item; 16 | 17 | 18 | - (BOOL) otherItemHasKey: (NSString*)key { 19 | JSONItem* otherItem = _item.parent[key]; 20 | return otherItem && otherItem != _item; 21 | } 22 | 23 | 24 | - (NSString *)stringForObjectValue: (id)obj { 25 | return [obj description]; 26 | } 27 | 28 | 29 | - (BOOL)getObjectValue:(out id *)obj 30 | forString:(NSString *)string 31 | errorDescription:(out NSString **)errorMessage 32 | { 33 | if (string.length == 0) { 34 | *obj = nil; 35 | return YES; 36 | } else if ([string hasPrefix: @"_"] && _item.parent.isRoot) { 37 | if (errorMessage) 38 | *errorMessage = @"Top-level properties may not start with an underscore ('_')."; 39 | return NO; 40 | } else if ([self otherItemHasKey: string]) { 41 | if (errorMessage) 42 | *errorMessage = @"That key already exists."; 43 | return NO; 44 | } else { 45 | *obj = string; 46 | return YES; 47 | } 48 | //TODO: Support numeric keys (for arrays) 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Source/QueryResultController.h: -------------------------------------------------------------------------------- 1 | // 2 | // QueryResultController.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 8/29/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QueryResultController : NSObject 12 | 13 | @property (copy) CBLQuery* query; 14 | @property (weak) NSOutlineView* outline; 15 | @property BOOL showDeleted; 16 | 17 | - (void) registerPath: (NSArray*)path forColumn: (NSTableColumn*)column; 18 | - (void) unregisterColumn: (NSTableColumn*)column; 19 | 20 | - (NSArray*) selectedDocuments; 21 | - (BOOL) selectDocument: (CBLDocument*)doc; 22 | 23 | - (IBAction) newDocument: (id)sender; 24 | - (IBAction) deleteDocument: (id)sender; 25 | - (IBAction) copy:(id)sender; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Source/QueryResultController.m: -------------------------------------------------------------------------------- 1 | // 2 | // QueryResultController.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 8/29/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "QueryResultController.h" 10 | #import "DocEditor.h" 11 | #import "JSONItem.h" 12 | 13 | 14 | @interface CBLDocument (NotExposed) 15 | - (void) forgetCurrentRevision; 16 | @end 17 | 18 | 19 | @interface QueryResultController () 20 | { 21 | @private 22 | CBLLiveQuery* _query; 23 | NSMutableArray* _rows; 24 | NSOutlineView* _docsOutline; 25 | NSMutableDictionary* _columnPaths; 26 | 27 | IBOutlet DocEditor* _docEditor; 28 | IBOutlet NSButton *_addDocButton, *_removeDocButton; 29 | } 30 | @end 31 | 32 | 33 | 34 | @implementation QueryResultController 35 | 36 | 37 | - (CBLQuery*)query { 38 | return _query; 39 | } 40 | 41 | - (void) setQuery: (CBLQuery*)query 42 | { 43 | if (_query) 44 | [_query removeObserver: self forKeyPath: @"rows"]; 45 | query.prefetch = YES; 46 | _query = [query asLiveQuery]; 47 | [_query addObserver: self forKeyPath: @"rows" 48 | options: NSKeyValueObservingOptionInitial 49 | context: NULL]; 50 | [_query addObserver: self forKeyPath: @"error" 51 | options: 0 52 | context: NULL]; 53 | [_query start]; 54 | } 55 | 56 | 57 | - (NSOutlineView*) outline { 58 | return _docsOutline; 59 | } 60 | 61 | - (void) setOutline: (NSOutlineView*)outline { 62 | _docsOutline.dataSource = nil; 63 | _docsOutline.delegate = nil; 64 | _docsOutline = outline; 65 | outline.dataSource = self; 66 | outline.delegate = self; 67 | [outline reloadData]; 68 | [self enableDocumentButtons]; 69 | } 70 | 71 | 72 | - (BOOL) showDeleted { 73 | return _query.allDocsMode == kCBLIncludeDeleted; 74 | } 75 | 76 | - (void) setShowDeleted:(BOOL)showDeleted { 77 | _query.allDocsMode = showDeleted ? kCBLIncludeDeleted : kCBLAllDocs; 78 | [_query start]; 79 | } 80 | 81 | 82 | - (void)dealloc 83 | { 84 | [_query removeObserver: self forKeyPath: @"rows"]; 85 | } 86 | 87 | 88 | - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 89 | change:(NSDictionary *)change context:(void *)context 90 | { 91 | if (object == _query) { 92 | if (_query.lastError) { 93 | [_docsOutline.window presentError: _query.lastError]; 94 | } else { 95 | NSArray* selection; 96 | CBLDocument* editedDoc = _docEditor.revision.document; 97 | if (editedDoc) 98 | selection = @[editedDoc]; 99 | else 100 | selection = self.selectedDocuments; 101 | 102 | CBLQueryEnumerator* rows = _query.rows; 103 | _rows = rows.allObjects.mutableCopy; 104 | if (_docsOutline.sortDescriptors) 105 | [_rows sortUsingDescriptors: _docsOutline.sortDescriptors]; 106 | [_docsOutline reloadData]; 107 | 108 | self.selectedDocuments = selection; 109 | 110 | if (editedDoc) { 111 | [editedDoc forgetCurrentRevision]; // workaround for CBL not detecting the change 112 | _docEditor.revision = editedDoc.currentRevision; 113 | } 114 | } 115 | } 116 | } 117 | 118 | 119 | - (void) outlineView:(NSOutlineView *)outlineView 120 | sortDescriptorsDidChange:(NSArray *)oldDescriptors 121 | { 122 | [_rows sortUsingDescriptors: outlineView.sortDescriptors]; 123 | [outlineView reloadData]; 124 | } 125 | 126 | 127 | - (NSArray*) selectedRows { 128 | NSIndexSet* selIndexes = [_docsOutline selectedRowIndexes]; 129 | NSUInteger count = selIndexes.count; 130 | NSMutableArray* sel = [NSMutableArray arrayWithCapacity: count]; 131 | [selIndexes enumerateIndexesUsingBlock: ^(NSUInteger idx, BOOL *stop) { 132 | CBLQueryRow* item = [self queryRowForItem: [self->_docsOutline itemAtRow: idx]]; 133 | [sel addObject: item]; 134 | }]; 135 | return sel; 136 | } 137 | 138 | 139 | - (NSArray*) selectedDocuments { 140 | NSMutableArray* docs = [NSMutableArray array]; 141 | for (CBLQueryRow* row in self.selectedRows) 142 | [docs addObject: row.document]; 143 | return docs; 144 | } 145 | 146 | 147 | - (void) setSelectedDocuments: (NSArray*)sel { 148 | NSMutableIndexSet* selIndexes = [NSMutableIndexSet indexSet]; 149 | for (CBLDocument* doc in sel) { 150 | CBLQueryRow* queryRow = [self queryRowForDocument: doc]; 151 | if (queryRow) { 152 | NSInteger row = [_docsOutline rowForItem: [self itemForQueryRow: queryRow]]; 153 | if (row >= 0) 154 | [selIndexes addIndex: row]; 155 | } 156 | } 157 | [_docsOutline selectRowIndexes: selIndexes byExtendingSelection: NO]; 158 | } 159 | 160 | 161 | - (BOOL) selectDocument: (CBLDocument*)doc { 162 | CBLQueryRow* queryRow = [self queryRowForDocument: doc]; 163 | if (queryRow) { 164 | NSInteger row = [_docsOutline rowForItem: [self itemForQueryRow: queryRow]]; 165 | if (row >= 0) { 166 | [_docsOutline selectRowIndexes: [NSIndexSet indexSetWithIndex: row] 167 | byExtendingSelection: NO]; 168 | return YES; 169 | } 170 | } 171 | return NO; 172 | } 173 | 174 | 175 | #pragma mark - DOCUMENT-LIST VIEW: 176 | 177 | 178 | - (CBLQueryRow*) queryRowForItem: (id)item { 179 | NSAssert([item isKindOfClass: [CBLQueryRow class]], @"Invalid outline item: %@", item); 180 | return (CBLQueryRow*)item; 181 | } 182 | 183 | 184 | - (id) itemForQueryRow: (CBLQueryRow*)row { 185 | NSParameterAssert(row != nil); 186 | return row; 187 | } 188 | 189 | 190 | - (CBLQueryRow*) queryRowForDocument: (CBLDocument*)doc { 191 | NSString* docID = doc.documentID; 192 | for (CBLQueryRow* row in _rows) { 193 | if ([row.documentID isEqualToString: docID]) 194 | return row; 195 | } 196 | return nil; 197 | } 198 | 199 | 200 | static NSString* formatRevision( NSString* revID ) { 201 | if (revID.length >= 2 && [revID characterAtIndex: 1] == '-') 202 | revID = [@" " stringByAppendingString: revID]; 203 | return revID; 204 | } 205 | 206 | static NSString* formatProperty( id property ) { 207 | if (!property) 208 | return nil; 209 | NSString* result =[CBLJSON stringWithJSONObject: property 210 | options: CBLJSONWritingAllowFragments 211 | error: NULL]; 212 | if (result.length > 200) 213 | result = [[result substringToIndex: 200] stringByAppendingString: @"…"]; 214 | return result; 215 | } 216 | 217 | 218 | - (void) registerPath: (NSArray*)path forColumn: (NSTableColumn*)column { 219 | if (!_columnPaths) 220 | _columnPaths = [NSMutableDictionary new]; 221 | _columnPaths[column.identifier] = path; 222 | } 223 | 224 | 225 | - (void) unregisterColumn: (NSTableColumn*)column { 226 | [_columnPaths removeObjectForKey: column.identifier]; 227 | } 228 | 229 | 230 | - (id)outlineView:(NSOutlineView *)outlineView 231 | objectValueForTableColumn:(NSTableColumn *)tableColumn 232 | byItem:(id)item 233 | { 234 | CBLQueryRow* row = [self queryRowForItem: item]; 235 | NSString* identifier = tableColumn.identifier; 236 | 237 | NSArray* path = _columnPaths[identifier]; 238 | if (path) { 239 | id value = [JSONItem itemAtPath: path inObject: row.documentProperties]; 240 | return formatProperty(value); 241 | } else { 242 | static NSArray* kColumnIDs; 243 | if (!kColumnIDs) 244 | kColumnIDs = @[@"id", @"rev", @"seq", @"json"]; 245 | switch ([kColumnIDs indexOfObject: identifier]) { 246 | case 0: return row.documentID; 247 | case 1: return formatRevision(row.documentRevisionID); 248 | case 2: return @(row.sequenceNumber); 249 | case 3: return formatProperty(row.document.userProperties); 250 | default:return @"???"; 251 | } 252 | } 253 | } 254 | 255 | 256 | - (void) outlineView:(NSOutlineView *)outlineView 257 | willDisplayCell:(id)cell 258 | forTableColumn:(NSTableColumn *)col 259 | item:(id)item 260 | { 261 | CBLQueryRow* row = [self queryRowForItem: item]; 262 | BOOL deleted = [row.value[@"deleted"] boolValue]; 263 | NSColor* color = deleted ? [NSColor disabledControlTextColor] 264 | : [NSColor controlTextColor]; 265 | [cell setTextColor: color]; 266 | } 267 | 268 | 269 | - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { 270 | return item == nil; 271 | } 272 | 273 | 274 | - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { 275 | if (item == nil) 276 | return _rows.count; 277 | else 278 | return 0; 279 | } 280 | 281 | 282 | - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { 283 | if (item == nil) 284 | return [self itemForQueryRow: _rows[index]]; 285 | else 286 | return nil; 287 | } 288 | 289 | 290 | - (BOOL)selectionShouldChangeInOutlineView:(NSOutlineView *)outlineView { 291 | return [_docEditor saveDocument]; 292 | } 293 | 294 | 295 | - (void)outlineViewSelectionDidChange:(NSNotification *)notification { 296 | [self enableDocumentButtons]; 297 | 298 | CBLQueryRow* sel = nil; 299 | NSIndexSet* selRows = [_docsOutline selectedRowIndexes]; 300 | if (selRows.count == 1) { 301 | id item = [_docsOutline itemAtRow: [selRows firstIndex]]; 302 | sel = item ? [self queryRowForItem: item] : nil; 303 | } 304 | _docEditor.readOnly = NO; 305 | [_docEditor setRevision: sel.document.currentRevision]; 306 | } 307 | 308 | 309 | #pragma mark - ACTIONS: 310 | 311 | 312 | - (void) enableDocumentButtons { 313 | _addDocButton.enabled = YES; 314 | _removeDocButton.enabled = (_docsOutline.selectedRow >= 0); 315 | } 316 | 317 | 318 | - (IBAction) newDocument: (id)sender { 319 | [_docsOutline deselectAll: nil]; 320 | [_docEditor editNewDocument]; 321 | } 322 | 323 | 324 | - (IBAction) deleteDocument: (id)sender { 325 | NSArray* sel = self.selectedDocuments; 326 | if (sel.count == 0) { 327 | NSBeep(); 328 | return; 329 | } 330 | __block NSError* error; 331 | BOOL ok = [_query.database inTransaction:^BOOL{ 332 | for (CBLDocument* doc in sel) { 333 | if (![doc deleteDocument: &error]) 334 | return NO; 335 | } 336 | return YES; 337 | }]; 338 | if (!ok) { 339 | [_docsOutline presentError: error]; 340 | } 341 | } 342 | 343 | 344 | - (IBAction) copy:(id)sender { 345 | NSArray* docs = self.selectedDocuments; 346 | if (!docs.count) { 347 | NSBeep(); 348 | return; 349 | } 350 | NSMutableArray* docIDs = [NSMutableArray array]; 351 | for (CBLDocument* doc in docs) 352 | [docIDs addObject: doc.documentID]; 353 | NSString* result = [docIDs componentsJoinedByString: @"\n"]; 354 | 355 | NSPasteboard* pb = [NSPasteboard generalPasteboard]; 356 | [pb clearContents]; 357 | [pb setString: result forType: NSStringPboardType]; 358 | } 359 | 360 | 361 | @end 362 | 363 | 364 | 365 | @interface NSString (QueryResultController) 366 | - (NSComparisonResult) revID_compare: (NSString*)str; 367 | @end 368 | 369 | @implementation NSString (QueryResultController) 370 | 371 | - (NSComparisonResult) revID_compare: (NSString*)str { 372 | return [self compare: str options: NSNumericSearch]; 373 | } 374 | 375 | @end 376 | -------------------------------------------------------------------------------- /Source/RevTreeController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RevTreeController.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 8/29/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RevTreeController : NSObject 12 | 13 | @property (weak) NSOutlineView* outline; 14 | @property (strong) CBLDocument* document; 15 | @property BOOL showDeleted; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Source/RevTreeController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RevTreeController.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 8/29/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import "RevTreeController.h" 10 | #import "DocEditor.h" 11 | #import "DocHistory.h" 12 | 13 | 14 | static NSFont* sFont, *sBoldFont; 15 | 16 | static void enableCell(NSTextFieldCell *cell, BOOL enabled); 17 | static void emboldenCell(NSTextFieldCell *cell, BOOL embolden); 18 | 19 | 20 | @interface RevTreeController () 21 | { 22 | CBLDocument* _document; 23 | NSTreeNode* _fullRoot; // includes deleted revs 24 | NSTreeNode* _root; // may not include deleted revs 25 | NSSet* _leaves; 26 | BOOL _showDeleted; 27 | 28 | IBOutlet DocEditor* _docEditor; 29 | IBOutlet NSOutlineView* _docsOutline; 30 | IBOutlet NSButton *_addDocButton, *_removeDocButton; 31 | } 32 | @end 33 | 34 | 35 | 36 | @implementation RevTreeController 37 | 38 | 39 | - (NSOutlineView*) outline { 40 | return _docsOutline; 41 | } 42 | 43 | - (void) setOutline: (NSOutlineView*)outline { 44 | if (_docsOutline && sFont) { 45 | // Restore font of rev column (may have been left bolded) 46 | NSTextFieldCell* cell = [_docsOutline tableColumnWithIdentifier: @"rev"].dataCell; 47 | cell.font = sFont; 48 | } 49 | _docsOutline.dataSource = nil; 50 | _docsOutline.delegate = nil; 51 | _docsOutline = outline; 52 | 53 | if (outline) { 54 | outline.dataSource = self; 55 | outline.delegate = self; 56 | [outline reloadData]; 57 | [outline expandItem: nil expandChildren: YES]; 58 | [self outlineViewSelectionDidChange]; 59 | _addDocButton.enabled = _removeDocButton.enabled = NO; 60 | } 61 | } 62 | 63 | 64 | - (CBLDocument*) document { 65 | return _document; 66 | } 67 | 68 | - (void) setDocument:(CBLDocument *)document { 69 | _document = document; 70 | _fullRoot = document ? GetDocRevisionTree(document) : nil; 71 | if (!_showDeleted && _document.currentRevision.isDeletion) 72 | self.showDeleted = YES; 73 | else 74 | [self updateRoot]; 75 | } 76 | 77 | 78 | - (void) updateRoot { 79 | _root = _showDeleted ? CopyTree(_fullRoot) : TreeWithoutDeletedBranches(_fullRoot); 80 | _leaves = GetLeafNodes(_root); 81 | FlattenTree(_root); 82 | } 83 | 84 | 85 | - (BOOL) showDeleted { 86 | return _showDeleted; 87 | } 88 | 89 | - (void) setShowDeleted:(BOOL)showDeleted { 90 | _showDeleted = showDeleted; 91 | [self updateRoot]; 92 | [_docsOutline reloadData]; 93 | } 94 | 95 | 96 | - (NSArray*) selectedRevisions { 97 | NSIndexSet* selIndexes = [_docsOutline selectedRowIndexes]; 98 | NSUInteger count = selIndexes.count; 99 | NSMutableArray* sel = [NSMutableArray arrayWithCapacity: count]; 100 | [selIndexes enumerateIndexesUsingBlock: ^(NSUInteger idx, BOOL *stop) { 101 | CBLSavedRevision* item = [self revisionForItem: [self->_docsOutline itemAtRow: idx]]; 102 | [sel addObject: item]; 103 | }]; 104 | return sel; 105 | } 106 | 107 | 108 | #pragma mark - REVISION-LIST VIEW: 109 | 110 | 111 | - (CBLSavedRevision*) revisionForItem: (id)item { 112 | NSAssert(item==nil || [item isKindOfClass: [NSTreeNode class]], @"Invalid outline item: %@", item); 113 | return [item representedObject]; 114 | } 115 | 116 | 117 | static NSString* formatRevision( NSString* revID ) { 118 | if (revID.length >= 2 && [revID characterAtIndex: 1] == '-') 119 | revID = [@" " stringByAppendingString: revID]; 120 | return revID; 121 | } 122 | 123 | static NSString* formatProperty( id property ) { 124 | return property ? [CBLJSON stringWithJSONObject: property options: 0 error: NULL] : nil; 125 | } 126 | 127 | 128 | - (id)outlineView:(NSOutlineView *)outlineView 129 | objectValueForTableColumn:(NSTableColumn *)tableColumn 130 | byItem:(id)item 131 | { 132 | CBLSavedRevision* rev = [self revisionForItem: item]; 133 | NSString* identifier = tableColumn.identifier; 134 | 135 | if ([identifier hasPrefix: @"."]) { 136 | NSString* property = [identifier substringFromIndex: 1]; 137 | return formatProperty(rev[property]); 138 | } else { 139 | static NSArray* kColumnIDs; 140 | if (!kColumnIDs) 141 | kColumnIDs = @[@"id", @"rev", @"json"]; 142 | switch ([kColumnIDs indexOfObject: identifier]) { 143 | case 0: return rev.document.documentID; 144 | case 1: return formatRevision(rev.revisionID); 145 | case 2: { 146 | if (!rev.propertiesAvailable) 147 | return @"missing"; 148 | NSDictionary* userProps = rev.userProperties; 149 | return userProps.count ? formatProperty(userProps) : nil; 150 | } 151 | default:return @"???"; 152 | } 153 | } 154 | } 155 | 156 | 157 | - (void) outlineView:(NSOutlineView *)outlineView 158 | willDisplayCell:(id)cell 159 | forTableColumn:(NSTableColumn *)col 160 | item:(id)item 161 | { 162 | NSString* identifier = col.identifier; 163 | CBLSavedRevision* rev = [self revisionForItem: item]; 164 | if ([identifier isEqualToString: @"rev"]) { 165 | enableCell(cell, !rev.isDeletion); 166 | emboldenCell(cell, [_leaves containsObject: item] && !rev.isDeletion); 167 | } else if ([identifier isEqualToString: @"json"]) { 168 | enableCell(cell, rev.propertiesAvailable); 169 | } 170 | } 171 | 172 | 173 | - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { 174 | item = item ?: _root; 175 | return ![item isLeaf]; 176 | } 177 | 178 | 179 | - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { 180 | item = item ?: _root; 181 | return [[item childNodes] count]; 182 | } 183 | 184 | 185 | - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { 186 | item = item ?: _root; 187 | return [item childNodes][index]; 188 | } 189 | 190 | 191 | - (BOOL)selectionShouldChangeInOutlineView:(NSOutlineView *)outlineView { 192 | return [_docEditor saveDocument]; 193 | } 194 | 195 | 196 | - (void)outlineViewSelectionDidChange:(NSNotification *)notification { 197 | [self outlineViewSelectionDidChange]; 198 | } 199 | 200 | - (void)outlineViewSelectionDidChange { 201 | CBLSavedRevision* sel = nil; 202 | NSIndexSet* selRows = [_docsOutline selectedRowIndexes]; 203 | if (selRows.count == 1) { 204 | id item = [_docsOutline itemAtRow: [selRows firstIndex]]; 205 | sel = item ? [self revisionForItem: item] : nil; 206 | } 207 | _docEditor.readOnly = YES; 208 | _docEditor.revision = sel; 209 | } 210 | 211 | 212 | #pragma mark - ACTIONS: 213 | 214 | 215 | - (IBAction) copy:(id)sender { 216 | NSArray* sel = self.selectedRevisions; 217 | if (!sel.count) { 218 | NSBeep(); 219 | return; 220 | } 221 | NSMutableArray* revIDs = [NSMutableArray array]; 222 | for (CBLSavedRevision* rev in sel) 223 | [revIDs addObject: rev.revisionID]; 224 | NSString* result = [revIDs componentsJoinedByString: @"\n"]; 225 | 226 | NSPasteboard* pb = [NSPasteboard generalPasteboard]; 227 | [pb clearContents]; 228 | [pb setString: result forType: NSStringPboardType]; 229 | } 230 | 231 | 232 | @end 233 | 234 | 235 | 236 | 237 | static void enableCell(NSTextFieldCell *cell, BOOL enabled) { 238 | NSColor* color = enabled ? [NSColor controlTextColor] 239 | : [NSColor disabledControlTextColor]; 240 | [cell setTextColor: color]; 241 | } 242 | 243 | static void emboldenCell(NSTextFieldCell *cell, BOOL embolden) { 244 | if (!sFont) { 245 | sFont = [cell font]; 246 | sBoldFont = [[NSFontManager sharedFontManager] convertFont: sFont 247 | toHaveTrait: NSBoldFontMask]; 248 | } 249 | [cell setFont: (embolden ? sBoldFont : sFont)]; 250 | } 251 | -------------------------------------------------------------------------------- /Source/URLFormatter.h: -------------------------------------------------------------------------------- 1 | // 2 | // URLFormatter.h 3 | // Couchbase Lite Viewer 4 | // 5 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | 11 | /** An NSFormatter for NSURL objects. 12 | It formats a file: URL as a plain path, other URLs in absolute form, and nil as an empty string. 13 | It intelligently parses user-entered URLs, turning absolute paths into file: URLs, 14 | or adding a missing "http" prefix if necessary. 15 | It also allows you to pop up a file picker, whose result will be entered as a path. */ 16 | @interface URLFormatter : NSFormatter 17 | { 18 | NSArray *_allowedSchemes; 19 | } 20 | 21 | @property (copy,nonatomic) NSArray *allowedSchemes; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Source/URLFormatter.m: -------------------------------------------------------------------------------- 1 | // 2 | // URLFormatter.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 6 | // 7 | 8 | #import "URLFormatter.h" 9 | 10 | 11 | @implementation URLFormatter 12 | 13 | @synthesize allowedSchemes=_allowedSchemes; 14 | 15 | 16 | - (id) init 17 | { 18 | self = [super init]; 19 | if (self != nil) { 20 | _allowedSchemes = @[@"http", @"https"]; 21 | } 22 | return self; 23 | } 24 | 25 | 26 | // EW: Needed to change implementation to handle relative paths. 27 | // Because this converted an NSURL (from an original string) to a NSString, 28 | // the information about whether the path was relative or not was lost. 29 | // So my changes allow for an NSString to not be converted to a NSURL 30 | // and then I just return the string if that's the case. This string will 31 | // be a relative path like @"../MySource". 32 | - (NSString *)stringForObjectValue:(id)obj 33 | { 34 | if( [obj isKindOfClass: [NSString class]] ) 35 | return obj; 36 | else if( ! [obj isKindOfClass: [NSURL class]] ) 37 | return @""; 38 | else if( [obj isFileURL] ) 39 | return [obj path]; 40 | else 41 | return [obj absoluteString]; 42 | } 43 | 44 | 45 | - (BOOL)getObjectValue:(id *)obj forString:(NSString *)str errorDescription:(NSString **)outError 46 | { 47 | *obj = nil; 48 | NSString *error = nil; 49 | if( str.length==0 ) { 50 | } else if( [str hasPrefix: @"/"] ) { 51 | *obj = [NSURL fileURLWithPath: str]; 52 | if( ! *obj ) 53 | error = @"Invalid filesystem path"; 54 | } else if( [str hasPrefix: @".."] ) { 55 | /* This check is needed for relative paths, e.g. ../MySource. 56 | A better implemention should be added to handle relative paths in the middle of the string. 57 | Instead of returning an NSURL, I return an NSString because this code gets called by 58 | stringForObjectValue which then converts it back to an NSString. The double conversion 59 | was causing information to be lost about whether the NSURL was a relative path or not. 60 | */ 61 | NSString* expanded_string = [str stringByStandardizingPath]; 62 | *obj = expanded_string; 63 | if( ! *obj ) 64 | error = @"Invalid filesystem path"; 65 | } else { 66 | NSURL *url = [NSURL URLWithString: str]; 67 | NSString *scheme = [url scheme]; 68 | if( url && scheme == nil ) { 69 | if( [str rangeOfString: @"."].length > 0 || [str rangeOfString: @":"].length > 0 ) { 70 | // Turn "foo.com/bar" into "http://foo.com/bar": 71 | str = [@"http://" stringByAppendingString: str]; 72 | url = [NSURL URLWithString: str]; 73 | scheme = [url scheme]; 74 | } else 75 | url = nil; 76 | } 77 | if( ! url || ! [url path] || url.host.length==0 ) { 78 | error = @"Invalid URL"; 79 | } else if( _allowedSchemes && ! [_allowedSchemes containsObject: scheme] ) { 80 | error = [@"URL protocol must be %@" stringByAppendingString: 81 | [_allowedSchemes componentsJoinedByString: @", "]]; 82 | } 83 | *obj = url; 84 | } 85 | if( outError ) *outError = error; 86 | return (error==nil); 87 | } 88 | 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /Source/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;} 3 | {\colortbl;\red255\green255\blue255;} 4 | \vieww9600\viewh8400\viewkind0 5 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\partightenfactor0 6 | 7 | \f0\b\fs22 \cf0 Engineering: 8 | \b0 \ 9 | Jens Alfke\ 10 | \ 11 | 12 | \b Icons: 13 | \b0 \ 14 | David Vignoni, from the Oxygen icon set} -------------------------------------------------------------------------------- /Source/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Source/en.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | -------------------------------------------------------------------------------- /Source/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Couchbase Lite Viewer 4 | // 5 | // Created by Jens Alfke on 4/2/12. 6 | // Copyright (c) 2012 Couchbase, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern void TestAppList(void);//TEMP 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | #if 0 16 | TestAppList(); 17 | return 0; 18 | #else 19 | return NSApplicationMain(argc, (const char **)argv); 20 | #endif 21 | } 22 | --------------------------------------------------------------------------------