Mhurd has submitted this change and it was merged.

Change subject: Old->new data importer
......................................................................


Old->new data importer

Notes on OldDataSchema:
* need -ObjC link option to not filter out 'unused' classes :P
  this can probably be removed if classes are marked used other ways
* separate OldDataSchemaBundle.bundle used to get the Core Data object 
definition
  into the app, this can be removed if switch to a framework bundle in iOS 
8-only
  future?

Still left to do in future:
* progress indicator
* fix or remove broken code for conversion from PhoneGap app

Change-Id: I7234f714b7c368da09b8a63295c623394dfbc70a
---
M MediaWikiKit/MediaWikiKit/MWKUserDataStore.h
M MediaWikiKit/MediaWikiKit/MWKUserDataStore.m
M OldDataSchema/Categories/Article+Convenience.h
M OldDataSchema/Data/ArticleDataContextSingleton.m
M OldDataSchema/Data/Model/ArticleCoreDataObjects.h
M OldDataSchema/OldDataSchema.xcodeproj/project.pbxproj
M OldDataSchema/OldDataSchema/OldDataSchema.h
M OldDataSchema/OldDataSchema/OldDataSchema.m
A OldDataSchema/OldDataSchemaBundle/Info.plist
M Wikipedia.xcodeproj/project.pbxproj
M Wikipedia.xcodeproj/xcshareddata/xcschemes/Wikipedia-iOS.xcscheme
R wikipedia/Data/DataMigrator.h
R wikipedia/Data/DataMigrator.m
R wikipedia/Data/SQLiteHelper.h
R wikipedia/Data/SQLiteHelper.m
A wikipedia/Data/SchemaConverter.h
A wikipedia/Data/SchemaConverter.m
M wikipedia/View Controllers/WebView/WebViewController.m
18 files changed, 642 insertions(+), 15 deletions(-)

Approvals:
  Mhurd: Verified; Looks good to me, approved



diff --git a/MediaWikiKit/MediaWikiKit/MWKUserDataStore.h 
b/MediaWikiKit/MediaWikiKit/MWKUserDataStore.h
index b245b04..eccae62 100644
--- a/MediaWikiKit/MediaWikiKit/MWKUserDataStore.h
+++ b/MediaWikiKit/MediaWikiKit/MWKUserDataStore.h
@@ -23,6 +23,7 @@
 -(instancetype)initWithDataStore:(MWKDataStore *)dataStore;
 
 -(void)save;
+-(void)reset;
 
 -(void)updateHistory:(MWKTitle *)title 
discoveryMethod:(MWKHistoryDiscoveryMethod)discoveryMethod;
 -(void)savePage:(MWKTitle *)title;
diff --git a/MediaWikiKit/MediaWikiKit/MWKUserDataStore.m 
b/MediaWikiKit/MediaWikiKit/MWKUserDataStore.m
index 7e0e440..85eb80b 100644
--- a/MediaWikiKit/MediaWikiKit/MWKUserDataStore.m
+++ b/MediaWikiKit/MediaWikiKit/MWKUserDataStore.m
@@ -27,6 +27,14 @@
     }
 }
 
+/// Clear out any currently loaded data and force it to be reloaded on next use
+-(void)reset
+{
+    _historyList = nil;
+    _savedPageList = nil;
+    _recentSearchList = nil;
+}
+
 -(instancetype)initWithDataStore:(MWKDataStore *)dataStore
 {
     self = [self init];
diff --git a/OldDataSchema/Categories/Article+Convenience.h 
b/OldDataSchema/Categories/Article+Convenience.h
index 4c6a86f..2f732af 100644
--- a/OldDataSchema/Categories/Article+Convenience.h
+++ b/OldDataSchema/Categories/Article+Convenience.h
@@ -1,6 +1,8 @@
 //  Created by Monte Hurd on 12/23/13.
 //  Copyright (c) 2013 Wikimedia Foundation. Provided under MIT-style license; 
please copy and modify!
 
+#import "UIKit/UIKit.h"
+
 #import "Article.h"
 
 @interface Article (Convenience)
diff --git a/OldDataSchema/Data/ArticleDataContextSingleton.m 
b/OldDataSchema/Data/ArticleDataContextSingleton.m
index de5091d..ca49ef0 100644
--- a/OldDataSchema/Data/ArticleDataContextSingleton.m
+++ b/OldDataSchema/Data/ArticleDataContextSingleton.m
@@ -49,7 +49,9 @@
     // Setup the masterContext and attach the persistant store to it.
     self.masterContext = [[NSManagedObjectContext alloc] 
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
     
-    NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel 
mergedModelFromBundles:nil];
+    NSString *bundlePath = [[NSBundle mainBundle] 
pathForResource:@"OldDataSchemaBundle" ofType:@"bundle"];
+    NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
+    NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel 
mergedModelFromBundles:@[bundle]];
     NSPersistentStoreCoordinator *persistentStoreCoordinator = 
[[NSPersistentStoreCoordinator alloc] 
initWithManagedObjectModel:managedObjectModel];
     
     NSString *articlesDBPath = [[self documentRootPath] 
stringByAppendingString:@"/articleData6.sqlite"];
diff --git a/OldDataSchema/Data/Model/ArticleCoreDataObjects.h 
b/OldDataSchema/Data/Model/ArticleCoreDataObjects.h
index 7ae506b..c7cda9b 100644
--- a/OldDataSchema/Data/Model/ArticleCoreDataObjects.h
+++ b/OldDataSchema/Data/Model/ArticleCoreDataObjects.h
@@ -1,6 +1,6 @@
 #import <CoreData/CoreData.h>
 
-//#import "Article+Convenience.h"
+#import "Article+Convenience.h"
 #import "DiscoveryContext.h"
 #import "Section.h"
 #import "History.h"
diff --git a/OldDataSchema/OldDataSchema.xcodeproj/project.pbxproj 
b/OldDataSchema/OldDataSchema.xcodeproj/project.pbxproj
index a663d97..2562281 100644
--- a/OldDataSchema/OldDataSchema.xcodeproj/project.pbxproj
+++ b/OldDataSchema/OldDataSchema.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
        objects = {
 
 /* Begin PBXBuildFile section */
+               D4E6D9111A5C65C0004916C1 /* ArticleData.xcdatamodeld in Sources 
*/ = {isa = PBXBuildFile; fileRef = D4F478531A48CE0100D8043C /* 
ArticleData.xcdatamodeld */; };
                D4F4782E1A48CD8500D8043C /* OldDataSchema.h in CopyFiles */ = 
{isa = PBXBuildFile; fileRef = D4F4782D1A48CD8500D8043C /* OldDataSchema.h */; 
};
                D4F478301A48CD8500D8043C /* OldDataSchema.m in Sources */ = 
{isa = PBXBuildFile; fileRef = D4F4782F1A48CD8500D8043C /* OldDataSchema.m */; 
};
                D4F478661A48CE0200D8043C /* ArticleDataContextSingleton.m in 
Sources */ = {isa = PBXBuildFile; fileRef = D4F4784E1A48CE0100D8043C /* 
ArticleDataContextSingleton.m */; };
@@ -36,6 +37,8 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+               D4E6D90A1A5C65B5004916C1 /* OldDataSchemaBundle.bundle */ = 
{isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 
0; path = OldDataSchemaBundle.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
+               D4E6D90D1A5C65B5004916C1 /* Info.plist */ = {isa = 
PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; 
sourceTree = "<group>"; };
                D4F4782A1A48CD8500D8043C /* libOldDataSchema.a */ = {isa = 
PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = 
libOldDataSchema.a; sourceTree = BUILT_PRODUCTS_DIR; };
                D4F4782D1A48CD8500D8043C /* OldDataSchema.h */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OldDataSchema.h; 
sourceTree = "<group>"; };
                D4F4782F1A48CD8500D8043C /* OldDataSchema.m */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = 
OldDataSchema.m; sourceTree = "<group>"; };
@@ -69,6 +72,13 @@
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
+               D4E6D9071A5C65B5004916C1 /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
                D4F478271A48CD8500D8043C /* Frameworks */ = {
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
@@ -79,10 +89,27 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+               D4E6D90B1A5C65B5004916C1 /* OldDataSchemaBundle */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D4E6D90C1A5C65B5004916C1 /* Supporting Files */,
+                       );
+                       path = OldDataSchemaBundle;
+                       sourceTree = "<group>";
+               };
+               D4E6D90C1A5C65B5004916C1 /* Supporting Files */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D4E6D90D1A5C65B5004916C1 /* Info.plist */,
+                       );
+                       name = "Supporting Files";
+                       sourceTree = "<group>";
+               };
                D4F478211A48CD8500D8043C = {
                        isa = PBXGroup;
                        children = (
                                D4F4782C1A48CD8500D8043C /* OldDataSchema */,
+                               D4E6D90B1A5C65B5004916C1 /* OldDataSchemaBundle 
*/,
                                D4F4782B1A48CD8500D8043C /* Products */,
                        );
                        sourceTree = "<group>";
@@ -91,6 +118,7 @@
                        isa = PBXGroup;
                        children = (
                                D4F4782A1A48CD8500D8043C /* libOldDataSchema.a 
*/,
+                               D4E6D90A1A5C65B5004916C1 /* 
OldDataSchemaBundle.bundle */,
                        );
                        name = Products;
                        sourceTree = "<group>";
@@ -150,6 +178,23 @@
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
+               D4E6D9091A5C65B5004916C1 /* OldDataSchemaBundle */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = D4E6D9101A5C65B5004916C1 /* 
Build configuration list for PBXNativeTarget "OldDataSchemaBundle" */;
+                       buildPhases = (
+                               D4E6D9061A5C65B5004916C1 /* Sources */,
+                               D4E6D9071A5C65B5004916C1 /* Frameworks */,
+                               D4E6D9081A5C65B5004916C1 /* Resources */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = OldDataSchemaBundle;
+                       productName = OldDataSchemaBundle;
+                       productReference = D4E6D90A1A5C65B5004916C1 /* 
OldDataSchemaBundle.bundle */;
+                       productType = "com.apple.product-type.bundle";
+               };
                D4F478291A48CD8500D8043C /* OldDataSchema */ = {
                        isa = PBXNativeTarget;
                        buildConfigurationList = D4F4783E1A48CD8500D8043C /* 
Build configuration list for PBXNativeTarget "OldDataSchema" */;
@@ -176,6 +221,9 @@
                                LastUpgradeCheck = 0610;
                                ORGANIZATIONNAME = "Wikimedia Foundation";
                                TargetAttributes = {
+                                       D4E6D9091A5C65B5004916C1 = {
+                                               CreatedOnToolsVersion = 6.1.1;
+                                       };
                                        D4F478291A48CD8500D8043C = {
                                                CreatedOnToolsVersion = 6.1.1;
                                        };
@@ -194,11 +242,30 @@
                        projectRoot = "";
                        targets = (
                                D4F478291A48CD8500D8043C /* OldDataSchema */,
+                               D4E6D9091A5C65B5004916C1 /* OldDataSchemaBundle 
*/,
                        );
                };
 /* End PBXProject section */
 
+/* Begin PBXResourcesBuildPhase section */
+               D4E6D9081A5C65B5004916C1 /* Resources */ = {
+                       isa = PBXResourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXResourcesBuildPhase section */
+
 /* Begin PBXSourcesBuildPhase section */
+               D4E6D9061A5C65B5004916C1 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               D4E6D9111A5C65C0004916C1 /* 
ArticleData.xcdatamodeld in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
                D4F478261A48CD8500D8043C /* Sources */ = {
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
@@ -221,6 +288,39 @@
 /* End PBXSourcesBuildPhase section */
 
 /* Begin XCBuildConfiguration section */
+               D4E6D90E1A5C65B5004916C1 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               COMBINE_HIDPI_IMAGES = YES;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "DEBUG=1",
+                                       "$(inherited)",
+                               );
+                               INFOPLIST_FILE = OldDataSchemaBundle/Info.plist;
+                               INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
+                               MACOSX_DEPLOYMENT_TARGET = 10.10;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SDKROOT = iphoneos;
+                               SKIP_INSTALL = YES;
+                               WRAPPER_EXTENSION = bundle;
+                       };
+                       name = Debug;
+               };
+               D4E6D90F1A5C65B5004916C1 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               COMBINE_HIDPI_IMAGES = YES;
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               INFOPLIST_FILE = OldDataSchemaBundle/Info.plist;
+                               INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
+                               MACOSX_DEPLOYMENT_TARGET = 10.10;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SDKROOT = iphoneos;
+                               SKIP_INSTALL = YES;
+                               WRAPPER_EXTENSION = bundle;
+                       };
+                       name = Release;
+               };
                D4F4783C1A48CD8500D8043C /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
@@ -254,6 +354,10 @@
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
+                               HEADER_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
+                               );
                                IPHONEOS_DEPLOYMENT_TARGET = 8.1;
                                MTL_ENABLE_DEBUG_INFO = YES;
                                ONLY_ACTIVE_ARCH = YES;
@@ -288,6 +392,10 @@
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
+                               HEADER_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
+                               );
                                IPHONEOS_DEPLOYMENT_TARGET = 8.1;
                                MTL_ENABLE_DEBUG_INFO = NO;
                                SDKROOT = iphoneos;
@@ -316,6 +424,14 @@
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
+               D4E6D9101A5C65B5004916C1 /* Build configuration list for 
PBXNativeTarget "OldDataSchemaBundle" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               D4E6D90E1A5C65B5004916C1 /* Debug */,
+                               D4E6D90F1A5C65B5004916C1 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+               };
                D4F478251A48CD8500D8043C /* Build configuration list for 
PBXProject "OldDataSchema" */ = {
                        isa = XCConfigurationList;
                        buildConfigurations = (
diff --git a/OldDataSchema/OldDataSchema/OldDataSchema.h 
b/OldDataSchema/OldDataSchema/OldDataSchema.h
index 77b5aa2..c74a4aa 100644
--- a/OldDataSchema/OldDataSchema/OldDataSchema.h
+++ b/OldDataSchema/OldDataSchema/OldDataSchema.h
@@ -14,6 +14,7 @@
 @protocol OldDataSchemaDelegate
 
 -(void)oldDataSchema:(OldDataSchema *)schema migrateArticle:(NSDictionary 
*)articleDict;
+-(void)oldDataSchema:(OldDataSchema *)schema migrateImage:(NSDictionary 
*)imageDict;
 -(void)oldDataSchema:(OldDataSchema *)schema migrateHistoryEntry:(NSDictionary 
*)historyDict;
 -(void)oldDataSchema:(OldDataSchema *)schema migrateSavedEntry:(NSDictionary 
*)savedDict;
 
@@ -26,5 +27,6 @@
 
 -(BOOL)exists;
 -(void)migrateData;
+-(void)removeOldData;
 
 @end
diff --git a/OldDataSchema/OldDataSchema/OldDataSchema.m 
b/OldDataSchema/OldDataSchema/OldDataSchema.m
index 8ad8d57..4190e04 100644
--- a/OldDataSchema/OldDataSchema/OldDataSchema.m
+++ b/OldDataSchema/OldDataSchema/OldDataSchema.m
@@ -14,19 +14,301 @@
 
 @implementation OldDataSchema {
     ArticleDataContextSingleton *context;
+    NSMutableSet *savedTitles;
 }
 
--(BOOL)exists
+-(instancetype)init
+{
+    self = [super init];
+    if (self) {
+        savedTitles = [[NSMutableSet alloc] init];
+        if (self.exists) {
+            context = [ArticleDataContextSingleton sharedInstance];
+        } else {
+            context = nil;
+        }
+    }
+    return self;
+}
+
+-(NSString *)sqlitePath
 {
     NSArray *documentPaths = 
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
     NSString *documentRootPath = [documentPaths objectAtIndex:0];
     NSString *filePath = [documentRootPath 
stringByAppendingPathComponent:@"articleData6.sqlite"];
+    return filePath;
+}
+
+-(BOOL)exists
+{
+    NSString *filePath = [self sqlitePath];
     return [[NSFileManager defaultManager] fileExistsAtPath:filePath];
 }
+
+-(void)removeOldData
+{
+    NSString *filePath = [self sqlitePath];
+    NSString *backupPath = [filePath stringByAppendingString:@".bak"];
+    NSError *err = nil;
+    [[NSFileManager defaultManager] moveItemAtPath:filePath
+                                            toPath:backupPath
+                                             error:&err];
+    if (err) {
+        NSLog(@"Error backing up %@: %@", filePath, err);
+    }
+}
+
 
 -(void)migrateData
 {
     // TODO
+    // 1) Go through saved article list, saving entries and (articles and 
images)
+    // 2) Go through page reading history, saving entries and (articles and 
images) when not already transferred
+    
+    NSFetchRequest *req = [NSFetchRequest fetchRequestWithEntityName:@"Saved"];
+    req.sortDescriptors = @[[[NSSortDescriptor alloc] initWithKey:@"dateSaved" 
ascending:YES]];
+    NSError *err;
+    NSArray *savedEntries = [context.mainContext executeFetchRequest:req 
error:&err];
+    if (err) {
+        NSLog(@"Error reading old Saved entries: %@", err);
+    }
+    for (Saved *saved in savedEntries) {
+        [self migrateSaved:saved];
+        [self migrateArticle:saved.article];
+    }
+    
+    NSFetchRequest *req2 = [NSFetchRequest 
fetchRequestWithEntityName:@"History"];
+    req2.sortDescriptors = @[[[NSSortDescriptor alloc] 
initWithKey:@"dateVisited" ascending:YES]];
+    NSError *err2;
+    NSArray *historyEntries = [context.mainContext executeFetchRequest:req2 
error:&err2];
+    if (err2) {
+        NSLog(@"Error reading old History entries: %@", err2);
+    }
+    for (History *history in historyEntries) {
+        [self migrateHistory:history];
+        [self migrateArticle:history.article];
+    }
+}
+
+-(void)migrateSaved:(Saved *)saved
+{
+    NSDictionary *dict = [self exportSaved:saved];
+    [self.delegate oldDataSchema:self migrateSavedEntry:dict];
+}
+
+-(void)migrateHistory:(History *)history
+{
+    NSDictionary *dict = [self exportHistory:history];
+    [self.delegate oldDataSchema:self migrateHistoryEntry:dict];
+}
+
+-(void)migrateArticle:(Article *)article
+{
+    NSString *key = [NSString stringWithFormat:@"%@:%@", article.domain, 
article.title];
+    if ([savedTitles containsObject:key]) {
+        // already imported this article
+    } else {
+        // Record for later to avoid dupe imports
+        [savedTitles addObject:key];
+
+        NSDictionary *dict = [self exportArticle:article];
+        [self.delegate oldDataSchema:self migrateArticle:dict];
+        
+        Image *thumbnail = article.thumbnailImage;
+        if (thumbnail) {
+            [self migrateThumbnailImage:thumbnail article:article];
+        }
+        
+        // Find its images...
+        for (Section *section in article.section) {
+            for (SectionImage *sectionImage in section.sectionImage) {
+                [self migrateImage:sectionImage];
+            }
+        }
+    }
+}
+
+-(void)migrateThumbnailImage:(Image *)thumbnailImage article:(Article *)article
+{
+    NSDictionary *dict = [self exportThumbnailImage:thumbnailImage 
article:article];
+    [self.delegate oldDataSchema:self migrateImage:dict];
+}
+
+-(void)migrateImage:(SectionImage *)sectionImage
+{
+    NSDictionary *dict = [self exportImage:sectionImage];
+    [self.delegate oldDataSchema:self migrateImage:dict];
+}
+
+-(NSDictionary *)exportSaved:(Saved *)saved
+{
+    return @{
+             @"domain": @"wikipedia.org",
+             @"language": saved.article.domain,
+             @"title": saved.article.title,
+             @"date": [self stringWithDate:saved.dateSaved]
+             };
+}
+
+-(NSDictionary *)exportHistory:(History *)history
+{
+    return @{
+             @"domain": @"wikipedia.org",
+             @"language": history.article.domain,
+             @"title": history.article.title,
+             @"date": [self stringWithDate:history.dateVisited],
+             @"discoveryMethod": history.discoveryMethod,
+             @"scrollPosition": history.article.lastScrollY
+             };
+}
+
+-(NSDictionary *)exportArticle:(Article *)article
+{
+    NSMutableDictionary *dict = [@{} mutableCopy];
+    
+    if (article.redirected) {
+        dict[@"redirected"] = article.redirected;
+    }
+    if (article.lastmodified) {
+        dict[@"lastmodified"] = [self stringWithDate:article.lastmodified];
+    }
+    if (article.lastmodifiedby) {
+        dict[@"lastmodifiedby"] = @{
+                                    @"name": article.lastmodifiedby,
+                                    @"gender": @"unknown"
+                                    };
+    }
+    if (article.articleId) {
+        dict[@"id"] = article.articleId;
+    }
+    if (article.languagecount) {
+        dict[@"languagecount"] = article.languagecount;
+    }
+    if (article.displayTitle) {
+        dict[@"displaytitle"] = article.displayTitle;
+    }
+    if (article.protectionStatus) {
+        dict[@"protection"] = @{
+                                @"edit": article.protectionStatus
+                                };
+    }
+    if (article.editable) {
+        dict[@"editable"] = @"";
+    }
+    
+    if (article.thumbnailImage) {
+        dict[@"thumbnailURL"] = article.thumbnailImage.sourceUrl;
+    }
+    
+    // sections!
+    NSUInteger numSections = [article.section count];
+    if (numSections) {
+        dict[@"sections"] = [[NSMutableArray alloc] 
initWithCapacity:numSections];
+        for (int i = 0; i < numSections; i++) {
+            dict[@"sections"][i] = [NSNull null]; // stub out
+        }
+        for (Section *section in article.section) {
+            int sectionId = [section.sectionId intValue];
+            dict[@"sections"][sectionId] = [self exportSection:section];
+        }
+    }
+    
+    return @{
+             @"language": article.domain,
+             @"title": article.title,
+             @"dict": dict
+             };
+}
+
+-(NSDictionary *)exportSection:(Section *)section
+{
+    NSMutableDictionary *dict = [@{} mutableCopy];
+    
+    if (section.tocLevel) {
+        dict[@"toclevel"] = section.tocLevel;
+    }
+    if (section.level) {
+        dict[@"level"] = section.level;
+    }
+    if (section.title) {
+        dict[@"line"] = section.title;
+    }
+    if (section.fromTitle) {
+        dict[@"fromtitle"] = section.fromTitle;
+    }
+    if (section.anchor) {
+        dict[@"anchor"] = section.anchor;
+    }
+    dict[@"id"] = section.sectionId;
+    if (section.html) {
+        dict[@"text"] = section.html;
+    }
+    
+    return dict;
+}
+
+-(NSDictionary *)exportThumbnailImage:(Image *)image article:(Article *)article
+{
+    ImageData *imageData = image.imageData;
+
+    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
+    
+    dict[@"domain"] = @"wikipedia.org";
+    dict[@"language"] = article.domain;
+    dict[@"title"] = article.title;
+
+    dict[@"sectionId"] = @(-1);
+
+    dict[@"sourceURL"] = image.sourceUrl;
+    if (imageData.data) {
+        dict[@"data"] = imageData.data;
+    }
+    
+    return dict;
+}
+
+-(NSDictionary *)exportImage:(SectionImage *)sectionImage
+{
+    Section *section = sectionImage.section;
+    Article *article = section.article;
+    Image *image = sectionImage.image;
+    ImageData *imageData = image.imageData;
+    
+    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
+    
+    dict[@"domain"] = @"wikipedia.org";
+    dict[@"language"] = article.domain;
+    dict[@"title"] = article.title;
+    
+    dict[@"sectionId"] = section.sectionId;
+    
+    dict[@"sourceURL"] = image.sourceUrl;
+    if (imageData.data) {
+        dict[@"data"] = imageData.data;
+    }
+    
+    return dict;
+}
+
+#pragma mark - date methods
+
+- (NSDateFormatter *)iso8601Formatter
+{
+    // See: https://www.mediawiki.org/wiki/Manual:WfTimestamp
+    NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
+    [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
+    [formatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
+    return formatter;
+}
+
+- (NSDate *)dateWithString:(NSString *)string
+{
+    return  [[self iso8601Formatter] dateFromString:string];
+}
+
+- (NSString *)stringWithDate:(NSDate *)date
+{
+    return [[self iso8601Formatter] stringFromDate:date];
 }
 
 @end
diff --git a/OldDataSchema/OldDataSchemaBundle/Info.plist 
b/OldDataSchema/OldDataSchemaBundle/Info.plist
new file mode 100644
index 0000000..6011a73
--- /dev/null
+++ b/OldDataSchema/OldDataSchemaBundle/Info.plist
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" 
"http://www.apple.com/DTDs/PropertyList-1.0.dtd";>
+<plist version="1.0">
+<dict>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>en</string>
+       <key>CFBundleExecutable</key>
+       <string>$(EXECUTABLE_NAME)</string>
+       <key>CFBundleIdentifier</key>
+       <string>org.wikimedia.$(PRODUCT_NAME:rfc1034identifier)</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundleName</key>
+       <string>$(PRODUCT_NAME)</string>
+       <key>CFBundlePackageType</key>
+       <string>BNDL</string>
+       <key>CFBundleShortVersionString</key>
+       <string>1.0</string>
+       <key>CFBundleSignature</key>
+       <string>????</string>
+       <key>CFBundleVersion</key>
+       <string>1</string>
+       <key>NSHumanReadableCopyright</key>
+       <string>Copyright © 2015 Wikimedia Foundation. All rights 
reserved.</string>
+       <key>NSPrincipalClass</key>
+       <string></string>
+</dict>
+</plist>
diff --git a/Wikipedia.xcodeproj/project.pbxproj 
b/Wikipedia.xcodeproj/project.pbxproj
index 426cc7e..11098ab 100644
--- a/Wikipedia.xcodeproj/project.pbxproj
+++ b/Wikipedia.xcodeproj/project.pbxproj
@@ -180,6 +180,7 @@
                04F27B7818FE0F2E00EDD838 /* PageHistoryViewController.m in 
Sources */ = {isa = PBXBuildFile; fileRef = 04F27B7418FE0F2E00EDD838 /* 
PageHistoryViewController.m */; };
                04F39590186CF80100B0D6FC /* TOCViewController.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 04F3958F186CF80100B0D6FC /* TOCViewController.m 
*/; };
                C9180EC418AED30C006C1DCA /* WikipediaAppUtils.m in Sources */ = 
{isa = PBXBuildFile; fileRef = C9180EC318AED30C006C1DCA /* WikipediaAppUtils.m 
*/; };
+               D407E6411A51DBDA00CCC8B1 /* SchemaConverter.m in Sources */ = 
{isa = PBXBuildFile; fileRef = D407E6401A51DBDA00CCC8B1 /* SchemaConverter.m 
*/; };
                D42E75EB18D11237002EA7E5 /* MWLanguageInfo.m in Sources */ = 
{isa = PBXBuildFile; fileRef = D42E75EA18D11237002EA7E5 /* MWLanguageInfo.m */; 
};
                D46CD8C418A1AC4F0042959E /* InfoPlist.strings in Resources */ = 
{isa = PBXBuildFile; fileRef = D46CD8C018A1AC4F0042959E /* InfoPlist.strings 
*/; };
                D46CD8C518A1AC4F0042959E /* Localizable.strings in Resources */ 
= {isa = PBXBuildFile; fileRef = D46CD8C218A1AC4F0042959E /* 
Localizable.strings */; };
@@ -201,8 +202,10 @@
                D4B0AE0819366A0A00F0AC90 /* CreateAccountFunnel.m in Sources */ 
= {isa = PBXBuildFile; fileRef = D4B0AE0719366A0A00F0AC90 /* 
CreateAccountFunnel.m */; };
                D4B0AE0B19366A2C00F0AC90 /* ReadingActionFunnel.m in Sources */ 
= {isa = PBXBuildFile; fileRef = D4B0AE0A19366A2C00F0AC90 /* 
ReadingActionFunnel.m */; };
                D4B0AE0E19366A5400F0AC90 /* LoginFunnel.m in Sources */ = {isa 
= PBXBuildFile; fileRef = D4B0AE0D19366A5400F0AC90 /* LoginFunnel.m */; };
+               D4B7794D1A5F36F700D06E00 /* OldDataSchemaBundle.bundle in 
Resources */ = {isa = PBXBuildFile; fileRef = D4E6D9161A5C65FA004916C1 /* 
OldDataSchemaBundle.bundle */; };
                D4BC22B4181E9E6300CAC673 /* empty.png in Resources */ = {isa = 
PBXBuildFile; fileRef = D4BC22B3181E9E6300CAC673 /* empty.png */; };
                D4C16A6819709CDF00CD91AD /* (null) in Resources */ = {isa = 
PBXBuildFile; };
+               D4E6D9121A5C65F9004916C1 /* CoreData.framework in Frameworks */ 
= {isa = PBXBuildFile; fileRef = 040E5C4E184566F4007AFE6F /* CoreData.framework 
*/; };
                D4E8A8A4190835C100DA4765 /* DataMigrator.m in Sources */ = {isa 
= PBXBuildFile; fileRef = D4E8A8A3190835C100DA4765 /* DataMigrator.m */; };
                D4E8A8A719084F1300DA4765 /* SQLiteHelper.m in Sources */ = {isa 
= PBXBuildFile; fileRef = D4E8A8A619084F1300DA4765 /* SQLiteHelper.m */; };
                D4E8A8A919085CEA00DA4765 /* libsqlite3.dylib in Frameworks */ = 
{isa = PBXBuildFile; fileRef = D4E8A8A819085CEA00DA4765 /* libsqlite3.dylib */; 
};
@@ -267,6 +270,13 @@
                        proxyType = 1;
                        remoteGlobalIDString = C73D7690357B1F8585BBD225;
                        remoteInfo = "Pods-hpple";
+               };
+               D4E6D9151A5C65FA004916C1 /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = D4F478441A48CD8500D8043C /* 
OldDataSchema.xcodeproj */;
+                       proxyType = 2;
+                       remoteGlobalIDString = D4E6D90A1A5C65B5004916C1;
+                       remoteInfo = OldDataSchemaBundle;
                };
                D4F478491A48CD8700D8043C /* PBXContainerItemProxy */ = {
                        isa = PBXContainerItemProxy;
@@ -496,8 +506,8 @@
                04AE1C6F1891B302002D5487 /* NSObject+Extras.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= "NSObject+Extras.m"; sourceTree = "<group>"; };
                04AE520319DB5E0900F89B92 /* NSObject+ConstraintsScale.h */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; 
path = "NSObject+ConstraintsScale.h"; sourceTree = "<group>"; };
                04AE520419DB5E0900F89B92 /* NSObject+ConstraintsScale.m */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; path = "NSObject+ConstraintsScale.m"; sourceTree = 
"<group>"; };
-               04B0EA43190AFDD8007458AF /* ArticleImporter.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
ArticleImporter.h; sourceTree = "<group>"; };
-               04B0EA44190AFDD8007458AF /* ArticleImporter.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= ArticleImporter.m; sourceTree = "<group>"; };
+               04B0EA43190AFDD8007458AF /* ArticleImporter.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = 
ArticleImporter.h; path = wikipedia/Importer/ArticleImporter.h; sourceTree = 
SOURCE_ROOT; };
+               04B0EA44190AFDD8007458AF /* ArticleImporter.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name 
= ArticleImporter.m; path = wikipedia/Importer/ArticleImporter.m; sourceTree = 
SOURCE_ROOT; };
                04B0EA46190B2319007458AF /* PreviewLicenseView.xib */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = 
PreviewLicenseView.xib; sourceTree = "<group>"; };
                04B0EA48190B2348007458AF /* PreviewLicenseView.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
PreviewLicenseView.h; sourceTree = "<group>"; };
                04B0EA49190B2348007458AF /* PreviewLicenseView.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= PreviewLicenseView.m; sourceTree = "<group>"; };
@@ -604,6 +614,8 @@
                04F3958F186CF80100B0D6FC /* TOCViewController.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= TOCViewController.m; sourceTree = "<group>"; };
                C9180EC218AED30C006C1DCA /* WikipediaAppUtils.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WikipediaAppUtils.h; sourceTree = "<group>"; };
                C9180EC318AED30C006C1DCA /* WikipediaAppUtils.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= WikipediaAppUtils.m; sourceTree = "<group>"; };
+               D407E63F1A51DBDA00CCC8B1 /* SchemaConverter.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = 
SchemaConverter.h; path = wikipedia/Data/SchemaConverter.h; sourceTree = 
SOURCE_ROOT; };
+               D407E6401A51DBDA00CCC8B1 /* SchemaConverter.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name 
= SchemaConverter.m; path = wikipedia/Data/SchemaConverter.m; sourceTree = 
SOURCE_ROOT; };
                D42E75E918D11237002EA7E5 /* MWLanguageInfo.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = 
MWLanguageInfo.h; path = "mw-support/MWLanguageInfo.h"; sourceTree = "<group>"; 
};
                D42E75EA18D11237002EA7E5 /* MWLanguageInfo.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name 
= MWLanguageInfo.m; path = "mw-support/MWLanguageInfo.m"; sourceTree = 
"<group>"; };
                D442F58619709E540013A2CA /* qqq */ = {isa = PBXFileReference; 
lastKnownFileType = text.plist.strings; name = qqq; path = 
qqq.lproj/Main_iPhone.strings; sourceTree = "<group>"; };
@@ -820,10 +832,10 @@
                D4C16A6119708B0E00CD91AD /* krc */ = {isa = PBXFileReference; 
lastKnownFileType = text.plist.strings; name = krc; path = 
krc.lproj/Main_iPhone.strings; sourceTree = "<group>"; };
                D4C16A6419709CDF00CD91AD /* qqq */ = {isa = PBXFileReference; 
lastKnownFileType = text.plist.strings; name = qqq; path = 
qqq.lproj/InfoPlist.strings; sourceTree = "<group>"; };
                D4C16A6519709CDF00CD91AD /* qqq */ = {isa = PBXFileReference; 
lastKnownFileType = text.plist.strings; name = qqq; path = 
qqq.lproj/Localizable.strings; sourceTree = "<group>"; };
-               D4E8A8A2190835C100DA4765 /* DataMigrator.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = 
DataMigrator.h; path = wikipedia/DataMigrator.h; sourceTree = SOURCE_ROOT; };
-               D4E8A8A3190835C100DA4765 /* DataMigrator.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name 
= DataMigrator.m; path = wikipedia/DataMigrator.m; sourceTree = SOURCE_ROOT; };
-               D4E8A8A519084F1300DA4765 /* SQLiteHelper.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
SQLiteHelper.h; sourceTree = "<group>"; };
-               D4E8A8A619084F1300DA4765 /* SQLiteHelper.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= SQLiteHelper.m; sourceTree = "<group>"; };
+               D4E8A8A2190835C100DA4765 /* DataMigrator.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = 
DataMigrator.h; path = wikipedia/Data/DataMigrator.h; sourceTree = SOURCE_ROOT; 
};
+               D4E8A8A3190835C100DA4765 /* DataMigrator.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name 
= DataMigrator.m; path = wikipedia/Data/DataMigrator.m; sourceTree = 
SOURCE_ROOT; };
+               D4E8A8A519084F1300DA4765 /* SQLiteHelper.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = 
SQLiteHelper.h; path = wikipedia/Data/SQLiteHelper.h; sourceTree = SOURCE_ROOT; 
};
+               D4E8A8A619084F1300DA4765 /* SQLiteHelper.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name 
= SQLiteHelper.m; path = wikipedia/Data/SQLiteHelper.m; sourceTree = 
SOURCE_ROOT; };
                D4E8A8A819085CEA00DA4765 /* libsqlite3.dylib */ = {isa = 
PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = 
libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; };
                D4F277F9194235A00032BA38 /* ProtectedEditAttemptFunnel.h */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; 
name = ProtectedEditAttemptFunnel.h; path = 
EventLogging/ProtectedEditAttemptFunnel.h; sourceTree = "<group>"; };
                D4F277FA194235A00032BA38 /* ProtectedEditAttemptFunnel.m */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; name = ProtectedEditAttemptFunnel.m; path = 
EventLogging/ProtectedEditAttemptFunnel.m; sourceTree = "<group>"; };
@@ -838,6 +850,7 @@
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               D4E6D9121A5C65F9004916C1 /* CoreData.framework 
in Frameworks */,
                                D4F478741A48D1B100D8043C /* libOldDataSchema.a 
in Frameworks */,
                                D497FCFB1A080810004A36A5 /* libMediaWikiKit.a 
in Frameworks */,
                                D4AD975019F99E9000957451 /* libPods-hpple.a in 
Frameworks */,
@@ -860,8 +873,9 @@
                                D4E8A8A11908357600DA4765 /* Data Migration */,
                                04B0EA42190AFDBA007458AF /* Importer */,
                        );
-                       path = Data;
-                       sourceTree = "<group>";
+                       name = Data;
+                       path = wikipedia/Data;
+                       sourceTree = SOURCE_ROOT;
                };
                0412CC5F192536580010E616 /* Root */ = {
                        isa = PBXGroup;
@@ -1289,7 +1303,7 @@
                        );
                        name = Importer;
                        path = ../Importer;
-                       sourceTree = "<group>";
+                       sourceTree = SOURCE_ROOT;
                };
                04B60509193522650007185A /* MenuButton */ = {
                        isa = PBXGroup;
@@ -1743,10 +1757,12 @@
                                D4E8A8A3190835C100DA4765 /* DataMigrator.m */,
                                D4E8A8A519084F1300DA4765 /* SQLiteHelper.h */,
                                D4E8A8A619084F1300DA4765 /* SQLiteHelper.m */,
+                               D407E63F1A51DBDA00CCC8B1 /* SchemaConverter.h 
*/,
+                               D407E6401A51DBDA00CCC8B1 /* SchemaConverter.m 
*/,
                        );
                        name = "Data Migration";
                        path = ..;
-                       sourceTree = "<group>";
+                       sourceTree = SOURCE_ROOT;
                };
                D4EE00BB182445670090790F /* mw-support */ = {
                        isa = PBXGroup;
@@ -1762,6 +1778,7 @@
                        isa = PBXGroup;
                        children = (
                                D4F4784A1A48CD8700D8043C /* libOldDataSchema.a 
*/,
+                               D4E6D9161A5C65FA004916C1 /* 
OldDataSchemaBundle.bundle */,
                        );
                        name = Products;
                        sourceTree = "<group>";
@@ -1952,6 +1969,13 @@
                        remoteRef = D4AD974819F99CD700957451 /* 
PBXContainerItemProxy */;
                        sourceTree = BUILT_PRODUCTS_DIR;
                };
+               D4E6D9161A5C65FA004916C1 /* OldDataSchemaBundle.bundle */ = {
+                       isa = PBXReferenceProxy;
+                       fileType = wrapper.cfbundle;
+                       path = OldDataSchemaBundle.bundle;
+                       remoteRef = D4E6D9151A5C65FA004916C1 /* 
PBXContainerItemProxy */;
+                       sourceTree = BUILT_PRODUCTS_DIR;
+               };
                D4F4784A1A48CD8700D8043C /* libOldDataSchema.a */ = {
                        isa = PBXReferenceProxy;
                        fileType = archive.ar;
@@ -1966,6 +1990,7 @@
                        isa = PBXResourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               D4B7794D1A5F36F700D06E00 /* 
OldDataSchemaBundle.bundle in Resources */,
                                04D3082C19991CB60034F106 /* 
logo-placeholder-nea...@2x.png in Resources */,
                                D46CD8C418A1AC4F0042959E /* InfoPlist.strings 
in Resources */,
                                045EFF1B19A25FEB00D0EDBB /* 
logo-placeholder-sea...@2x.png in Resources */,
@@ -2184,6 +2209,7 @@
                                047801BE18AE987900DBB747 /* 
UIButton+ColorMask.m in Sources */,
                                0429300A18604898002A13FC /* 
SavedPagesResultCell.m in Sources */,
                                0487048419F8262600B7D307 /* CaptchaResetter.m 
in Sources */,
+                               D407E6411A51DBDA00CCC8B1 /* SchemaConverter.m 
in Sources */,
                                04821CD119895EDC007558F6 /* 
ReferenceGradientView.m in Sources */,
                                0460F8DC19B0F932001BC59B /* CenteredPathView.m 
in Sources */,
                                0472BC18193AD88C00C40BDA /* 
MWKSection+DisplayHtml.m in Sources */,
@@ -2551,10 +2577,12 @@
                                        
"$(PROJECT_DIR)/cocoapods/Pods/Headers/Public/AFNetworking",
                                        
"$(PROJECT_DIR)/cocoapods/Pods/Headers/Public/hpple",
                                        
"$(PROJECT_DIR)/MediaWikiKit/MediaWikiKit",
+                                       "$(PROJECT_DIR)/OldDataSchema/**",
                                );
                                INFOPLIST_FILE = 
"Wikipedia/Wikipedia-Info.plist";
                                IPHONEOS_DEPLOYMENT_TARGET = 6.0;
                                LIBRARY_SEARCH_PATHS = "$(inherited)";
+                               OTHER_LDFLAGS = "-ObjC";
                                PRODUCT_NAME = Wikipedia;
                                PROVISIONING_PROFILE = "";
                                WRAPPER_EXTENSION = app;
@@ -2648,10 +2676,12 @@
                                        
"$(PROJECT_DIR)/cocoapods/Pods/Headers/Public/AFNetworking",
                                        
"$(PROJECT_DIR)/cocoapods/Pods/Headers/Public/hpple",
                                        
"$(PROJECT_DIR)/MediaWikiKit/MediaWikiKit",
+                                       "$(PROJECT_DIR)/OldDataSchema/**",
                                );
                                INFOPLIST_FILE = 
"Wikipedia/Wikipedia-Info.plist";
                                IPHONEOS_DEPLOYMENT_TARGET = 6.0;
                                LIBRARY_SEARCH_PATHS = "$(inherited)";
+                               OTHER_LDFLAGS = "-ObjC";
                                PRODUCT_NAME = Wikipedia;
                                PROVISIONING_PROFILE = "";
                                WRAPPER_EXTENSION = app;
@@ -2674,10 +2704,12 @@
                                        
"$(PROJECT_DIR)/cocoapods/Pods/Headers/Public/AFNetworking",
                                        
"$(PROJECT_DIR)/cocoapods/Pods/Headers/Public/hpple",
                                        
"$(PROJECT_DIR)/MediaWikiKit/MediaWikiKit",
+                                       "$(PROJECT_DIR)/OldDataSchema/**",
                                );
                                INFOPLIST_FILE = 
"Wikipedia/Wikipedia-Info.plist";
                                IPHONEOS_DEPLOYMENT_TARGET = 6.0;
                                LIBRARY_SEARCH_PATHS = "$(inherited)";
+                               OTHER_LDFLAGS = "-ObjC";
                                PRODUCT_NAME = Wikipedia;
                                PROVISIONING_PROFILE = "";
                                WRAPPER_EXTENSION = app;
diff --git a/Wikipedia.xcodeproj/xcshareddata/xcschemes/Wikipedia-iOS.xcscheme 
b/Wikipedia.xcodeproj/xcshareddata/xcschemes/Wikipedia-iOS.xcscheme
index 2ddce9c..3bbc795 100644
--- a/Wikipedia.xcodeproj/xcshareddata/xcschemes/Wikipedia-iOS.xcscheme
+++ b/Wikipedia.xcodeproj/xcshareddata/xcschemes/Wikipedia-iOS.xcscheme
@@ -34,6 +34,20 @@
                ReferencedContainer = "container:Wikipedia.xcodeproj">
             </BuildableReference>
          </BuildActionEntry>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "D4E6D9091A5C65B5004916C1"
+               BuildableName = "OldDataSchemaBundle.bundle"
+               BlueprintName = "OldDataSchemaBundle"
+               ReferencedContainer = 
"container:OldDataSchema/OldDataSchema.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
       </BuildActionEntries>
    </BuildAction>
    <TestAction
diff --git a/wikipedia/DataMigrator.h b/wikipedia/Data/DataMigrator.h
similarity index 100%
rename from wikipedia/DataMigrator.h
rename to wikipedia/Data/DataMigrator.h
diff --git a/wikipedia/DataMigrator.m b/wikipedia/Data/DataMigrator.m
similarity index 100%
rename from wikipedia/DataMigrator.m
rename to wikipedia/Data/DataMigrator.m
diff --git a/wikipedia/SQLiteHelper.h b/wikipedia/Data/SQLiteHelper.h
similarity index 100%
rename from wikipedia/SQLiteHelper.h
rename to wikipedia/Data/SQLiteHelper.h
diff --git a/wikipedia/SQLiteHelper.m b/wikipedia/Data/SQLiteHelper.m
similarity index 100%
rename from wikipedia/SQLiteHelper.m
rename to wikipedia/Data/SQLiteHelper.m
diff --git a/wikipedia/Data/SchemaConverter.h b/wikipedia/Data/SchemaConverter.h
new file mode 100644
index 0000000..25e854f
--- /dev/null
+++ b/wikipedia/Data/SchemaConverter.h
@@ -0,0 +1,23 @@
+//
+//  SchemaConverter.h
+//  Wikipedia
+//
+//  Created by Brion on 12/29/14.
+//  Copyright (c) 2014 Wikimedia Foundation. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "OldDataSchema.h"
+
+#import "MediaWikiKit.h"
+
+@interface SchemaConverter : NSObject <OldDataSchemaDelegate>
+
+@property OldDataSchema *schema;
+@property MWKDataStore *dataStore;
+@property MWKUserDataStore *userDataStore;
+
+-(instancetype)initWithDataStore:(MWKDataStore *)dataStore;
+
+@end
diff --git a/wikipedia/Data/SchemaConverter.m b/wikipedia/Data/SchemaConverter.m
new file mode 100644
index 0000000..45bda53
--- /dev/null
+++ b/wikipedia/Data/SchemaConverter.m
@@ -0,0 +1,92 @@
+//
+//  SchemaConverter.m
+//  Wikipedia
+//
+//  Created by Brion on 12/29/14.
+//  Copyright (c) 2014 Wikimedia Foundation. All rights reserved.
+//
+
+#import "SchemaConverter.h"
+
+@implementation SchemaConverter
+
+-(instancetype)initWithDataStore:(MWKDataStore *)dataStore
+{
+    self = [super init];
+    if (self) {
+        self.dataStore = dataStore;
+        self.userDataStore = [self.dataStore userDataStore];
+    }
+    return self;
+}
+
+-(void)oldDataSchema:(OldDataSchema *)schema migrateArticle:(NSDictionary 
*)articleDict
+{
+    NSString *language = articleDict[@"language"];
+    NSString *titleStr = articleDict[@"title"];
+    NSDictionary *mobileview = articleDict[@"dict"];
+
+    MWKSite *site = [[MWKSite alloc] initWithDomain:@"wikipedia.org" 
language:language];
+    MWKTitle *title = [site titleWithString:titleStr];
+    MWKArticle *article = [self.dataStore articleWithTitle:title];
+    [article importMobileViewJSON:mobileview];
+    [article save];
+}
+
+-(void)oldDataSchema:(OldDataSchema *)schema migrateImage:(NSDictionary 
*)imageDict
+{
+    NSString *language = imageDict[@"language"];
+    NSString *titleStr = imageDict[@"title"];
+    NSString *sourceURL = imageDict[@"sourceURL"];
+    int sectionId = [imageDict[@"sectionId"] intValue];
+    NSData *imageData = imageDict[@"data"];
+
+    // @todo cache the article object?
+    MWKSite *site = [[MWKSite alloc] initWithDomain:@"wikipedia.org" 
language:language];
+    MWKTitle *title = [site titleWithString:titleStr];
+    MWKArticle *article = [self.dataStore articleWithTitle:title];
+    
+    MWKImage *image = [article importImageURL:sourceURL sectionId:sectionId];
+    [image importImageData:imageData];
+}
+
+-(void)oldDataSchema:(OldDataSchema *)schema migrateHistoryEntry:(NSDictionary 
*)historyDict
+{
+    NSString *language = historyDict[@"language"];
+    NSString *titleStr = historyDict[@"title"];
+    NSString *date = historyDict[@"date"];
+    NSString *discoveryMethod = historyDict[@"discoveryMethod"];
+    
+    NSMutableDictionary *dict = [@{} mutableCopy];
+    dict[@"domain"] = @"wikipedia.org";
+    dict[@"language"] = language;
+    dict[@"title"] = titleStr;
+    dict[@"date"] = date;
+    dict[@"discoveryMethod"] = discoveryMethod;
+    dict[@"scrollPosition"] = @(0); // @fixme extract from article?
+    
+    MWKHistoryEntry *entry = [[MWKHistoryEntry alloc] initWithDict:dict];
+    
+    MWKHistoryList *historyList = self.userDataStore.historyList;
+    [historyList addEntry:entry];
+    [self.userDataStore save];
+}
+
+-(void)oldDataSchema:(OldDataSchema *)schema migrateSavedEntry:(NSDictionary 
*)savedDict
+{
+    NSString *language = savedDict[@"language"];
+    NSString *titleStr = savedDict[@"title"];
+    
+    NSMutableDictionary *dict = [@{} mutableCopy];
+    dict[@"domain"] = @"wikipedia.org";
+    dict[@"language"] = language;
+    dict[@"title"] = titleStr;
+    
+    MWKSavedPageEntry *entry = [[MWKSavedPageEntry alloc] initWithDict:dict];
+    
+    MWKSavedPageList *savedPageList = self.userDataStore.savedPageList;
+    [savedPageList addEntry:entry];
+    [self.userDataStore save];
+}
+
+@end
diff --git a/wikipedia/View Controllers/WebView/WebViewController.m 
b/wikipedia/View Controllers/WebView/WebViewController.m
index 61fe777..cadcd34 100644
--- a/wikipedia/View Controllers/WebView/WebViewController.m
+++ b/wikipedia/View Controllers/WebView/WebViewController.m
@@ -54,6 +54,8 @@
 #import "AssetsFileFetcher.h"
 
 #import "LeadImageContainer.h"
+#import "OldDataSchema.h"
+#import "SchemaConverter.h"
 
 //#import "UIView+Debugging.h"
 
@@ -1849,6 +1851,28 @@
 
 - (void)migrateDataIfNecessary
 {
+    // Middle-Ages Converter
+    // From the native app's initial CoreData-based implementation,
+    // which now lives in OldDataSchema subproject.
+    OldDataSchema *oldDataSchema = [[OldDataSchema alloc] init];
+    if ([oldDataSchema exists]) {
+        SchemaConverter *schemaConverter = [[SchemaConverter alloc] 
initWithDataStore:session.dataStore];
+        oldDataSchema.delegate = schemaConverter;
+        NSLog(@"begin migration");
+        [oldDataSchema migrateData];
+        NSLog(@"end migration");
+        
+        [oldDataSchema removeOldData];
+        
+        // hack for history fix
+        [session.userDataStore reset];
+        
+        return;
+    }
+        
+    // Ye Ancient Converter
+    // From the old PhoneGap app
+    // @fixme: fix this to work again
     DataMigrator *dataMigrator = [[DataMigrator alloc] init];
     if ([dataMigrator hasData]) {
         NSLog(@"Old data to migrate found!");
@@ -1862,10 +1886,11 @@
         [importer importArticles:titles];
         
         [dataMigrator removeOldData];
-    } else {
-        NSLog(@"No old data to migrate.");
+
+        return;
     }
 
+    NSLog(@"No old data to migrate.");
 }
 
 #pragma mark Bottom menu bar

-- 
To view, visit https://gerrit.wikimedia.org/r/183091
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I7234f714b7c368da09b8a63295c623394dfbc70a
Gerrit-PatchSet: 9
Gerrit-Project: apps/ios/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Brion VIBBER <br...@wikimedia.org>
Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org>
Gerrit-Reviewer: Dr0ptp4kt <ab...@wikimedia.org>
Gerrit-Reviewer: Mhurd <mh...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to