Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Allow setting URL scheme templates #7485

Closed
wants to merge 1 commit into from
Closed

Conversation

kkaefer
Copy link
Contributor

@kkaefer kkaefer commented Dec 19, 2016

We are currently using mapbox:// URLs, but they're hardcoded into the system. This PR adds a generic setURLSchemeTemplate to OnlineFileSource and DefaultFileSource that allows you to specify templates for your own URL schemes.

@mention-bot
Copy link

@kkaefer, thanks for your PR! By analyzing this pull request, we identified @jfirebaugh, @tmcw and @yhahn to be potential reviewers.

@kkaefer
Copy link
Contributor Author

kkaefer commented Dec 19, 2016

(based on #7464)

@kkaefer kkaefer force-pushed the 7485-url-transforms branch 2 times, most recently from f774c97 to 30d0f67 Compare December 20, 2016 11:16
scheme without trailing `://`, e.g. just `example`. You may use arbitrary
alphanumeric schemes, with the exception of the protected scheme `mapbox`.
*/
- (void)setURLSchemeTemplate:(nullable NSString *)tpl forScheme:(NSString *)scheme;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“Scheme” is redundant here. A better name would be -setURLTemplate:forScheme: or -setTemplate:forURLScheme:.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right; this was a victim of automated text replacement (the previous name was -setURLTemplate:forScheme:). I'll change this to -setTemplate:forURLScheme:

* `{path}`: `"foo/bar/baz.png"` (without leading `/`)
* `{directory}`: `"foo/bar/"` (with trailing `/`)
* `{filename}`: `"baz"` (without file extension)
* `{extension}`: `".png"` (optionally with a preceding `@2x` ratio specifier)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if clang and jazzy are able to handle this nested list.

* `{path}`: `"foo/bar/baz.png"` (without leading `/`)
* `{directory}`: `"foo/bar/"` (with trailing `/`)
* `{filename}`: `"baz"` (without file extension)
* `{extension}`: `".png"` (optionally with a preceding `@2x` ratio specifier)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mini-languages in strings are un-Cocoa. Instead of exposing yet another mini-language that resembles but works differently than textField tokens in the runtime styling API, have you considered a lambda-based (block-based) API? The developer could specify a block (or perhaps an NSValueTransformer) that accepts an NSURL and returns a transformed NSURL. Within the block, they could easily parse and manipulate the URL via NSURLComponents.

Even more natural would be a delegate pattern: the developer could have their application delegate conform to a new MGLOfflineStorageDelegate protocol by implementing an -offlineStorage:transformedURLForTileURL: method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While that may be true, using platform-specific URL parsing code also introduces a source for potential platform deviation. Therefore, I'd prefer using the same code for all platforms.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Platform deviation would certainly be a problem when it comes to parsing URLs in JSON files, but a block would consist of platform-specific (or at least language-specific) code. Any deviation from how URLs are handled on the platform or in the language’s standard library would be more of a surprise, wouldn’t it?

In the unlikely event that incompatibilities between standard URL parsers on different platforms poses a problem to the developer, they could do minimal parsing inline. For most scenarios, however, a callback would enable developers to bring more context into the transformation logic without requiring us to add a special case for every innovation in tile URL technology.

Even if you don’t want to leave URL parsing up to the developer, why not have the block accept a dictionary mapping e.g. directory to foo/bar/ and allow the developer to perform concatenation and any other logic themselves?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Google and Apple both provide callback or block-based APIs on their analogues to our raster source concept: #7471 (comment).

@@ -238,6 +238,31 @@ typedef void (^MGLOfflinePackRemovalCompletionHandler)(NSError * _Nullable error
*/
@property (nonatomic, readonly) unsigned long long countOfBytesCompleted;

/**
Sets a URL template for the given scheme.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn’t clear whether this API affects only tile URLs or also style URLs, sprite sheet URLs, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how Any URLs encountered in styles and subsequently loaded sources could be any clearer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gah, don’t know how I missed that. 😆 I do think it would be helpful, for those who aren’t familiar with the resources that make up a style, to know exactly what “any URLs” entails. But that points to the need for a conceptual “what’s a style” guide in the documentation.

@kkaefer
Copy link
Contributor Author

kkaefer commented Jan 4, 2017

An alternative implementation (which is still lacking Android support) is in https://github.com/mapbox/mapbox-gl-native/compare/url-transform. It uses a function callback that allows manipulation of the Resource/URL.

Open questions are:

  • the delegate's method is called from the FileSource thread. Is that idiomatic in Cocoa? Using a synchronous dispatch to the main thread is impossible due to deadlocks.
  • Is the setter implemented correctly?

Usage is (e.g. in AppDelegate.m):

diff --git a/platform/macos/app/AppDelegate.h b/platform/macos/app/AppDelegate.h
index a1d9297..8d3e284 100644
--- a/platform/macos/app/AppDelegate.h
+++ b/platform/macos/app/AppDelegate.h
@@ -2,7 +2,7 @@
 
 extern NSString * const MGLMapboxAccessTokenDefaultsKey;
 
-@interface AppDelegate : NSObject <NSApplicationDelegate>
+@interface AppDelegate : NSObject <NSApplicationDelegate, MGLOfflineStorageDelegate>
 
 @property (weak) IBOutlet NSWindow *preferencesWindow;
 
diff --git a/platform/macos/app/AppDelegate.m b/platform/macos/app/AppDelegate.m
index d3fe2d2..cef3c1e 100644
--- a/platform/macos/app/AppDelegate.m
+++ b/platform/macos/app/AppDelegate.m
@@ -130,10 +130,19 @@ NSString * const MGLLastMapDebugMaskDefaultsKey = @"MGLLastMapDebugMask";
         [alert runModal];
         [self showPreferences:nil];
     }
+
+    [MGLOfflineStorage sharedOfflineStorage].delegate = self;
     
     [self.offlinePacksArrayController bind:@"content" toObject:[MGLOfflineStorage sharedOfflineStorage] withKeyPath:@"packs" options:nil];
 }
 
+- (NSURLComponents *)offlineStorage:(MGLOfflineStorage *)storage transformURL:(NSURLComponents *)url ofKind:(MGLResourceKind)kind {
+    if ([url.host isEqual: @"api.mapbox.com"]) {
+        url.host = @"a.tiles.mapbox.com";
+    }
+    return url;
+}
+
 - (void)applicationWillTerminate:(NSNotification *)notification {
     if (![[NSUserDefaults standardUserDefaults] boolForKey:@"NSQuitAlwaysKeepsWindows"]) {
         NSDocument *currentDocument = [NSDocumentController sharedDocumentController].currentDocument;

@1ec5
Copy link
Contributor

1ec5 commented Jan 4, 2017

It’s an antipattern in Objective-C (and arguably in general) for different methods on the same class to be called on different threads. That forces the class to explicitly synchronize any shared state such as ivars and avoid implicitly capturing self in any blocks. I’ve never seen it done successfully but, conversely, have had to tear such code out many times due to concurrency bugs.

If there’s no way to call a method on the main thread, the delegate cannot be AppDelegate. We should strongly recommend that the developer instead create a helper object – a subclass of NSObject that conforms to MGLOfflineStorageDelegate. Either AppDelegate (or some other application class) would own this object, or MGLOfflineStorage would own it – in which case we’d call it something else like MGLURLProtocol or even make use of the NSURLProtocol system.

@1ec5
Copy link
Contributor

1ec5 commented Jan 8, 2017

Related: #7455.

tokens, e.g. for `"http://example.com/foo/bar/baz.png"`, valid tokens are:
* `{scheme}`: `"http"`
* `{domain}`: `"example.com"`
* `{path}`: `"foo/bar/baz.png"` (without leading `/`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the path without leading /? That is inconsistent with the definitions of path elsewhere, e.g. window.Location, node, RFC 3986.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added pull request to change this in #7750

@jfirebaugh
Copy link
Contributor

Merged #8084 instead.

@jfirebaugh jfirebaugh closed this Feb 20, 2017
@jfirebaugh jfirebaugh deleted the 7485-url-transforms branch February 20, 2017 20:19
@1ec5 1ec5 mentioned this pull request Nov 3, 2017
7 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants