Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ImageLoader): Adds prefetch cancellation and cache query #996

Merged
merged 3 commits into from
Feb 10, 2017
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix(ImageLoader): Adds prefetch cancellation and cache query
Recent update to the UWP Community Toolkit allows us to cancel prefetch operations. This changeset also adds the ability to query for images on disk, but not yet in-memory.
rozele committed Feb 9, 2017

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit b7547aec5297fe5bdee9c30d65eab6b188c53ef6
86 changes: 74 additions & 12 deletions Libraries/Image/Image.windows.js
Original file line number Diff line number Diff line change
@@ -11,11 +11,11 @@
*/
'use strict';

const NativeMethodsMixin = require('NativeMethodsMixin');
var NativeMethodsMixin = require('NativeMethodsMixin');
var NativeModules = require('NativeModules');
var ImageResizeMode = require('ImageResizeMode');
var ImageStylePropTypes = require('ImageStylePropTypes');
var PropTypes = require('react/lib/ReactPropTypes');
var ViewStylePropTypes = require('ViewStylePropTypes');
var React = require('React');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
var StyleSheet = require('StyleSheet');
@@ -26,11 +26,19 @@ var flattenStyle = require('flattenStyle');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
var resolveAssetSource = require('resolveAssetSource');
var Set = require('Set');
var filterObject = require('fbjs/lib/filterObject');

var PropTypes = React.PropTypes;
var {
ImageLoader,
} = NativeModules;

let _requestId = 1;
function generateRequestId() {
return _requestId++;
}

/**
* <Image> - A react component for displaying different types of images,
* including network images, static resources, temporary local images, and
@@ -63,6 +71,9 @@ var ImageViewAttributes = merge(ReactNativeViewAttributes.UIView, {
shouldNotifyLoadEvents: true,
});

var ViewStyleKeys = new Set(Object.keys(ViewStylePropTypes));
var ImageSpecificStyleKeys = new Set(Object.keys(ImageStylePropTypes).filter(x => !ViewStyleKeys.has(x)));

var Image = React.createClass({
propTypes: {
...View.propTypes,
@@ -107,6 +118,10 @@ var Image = React.createClass({
* Invoked on load start
*/
onLoadStart: PropTypes.func,
/**
* Invoked on load error
*/
onError: PropTypes.func,
/**
* Invoked when load completes successfully
*/
@@ -119,6 +134,26 @@ var Image = React.createClass({
* Used to locate this view in end-to-end tests.
*/
testID: PropTypes.string,
/**
* Determines how to resize the image when the frame doesn't match the raw
* image dimensions.
*
* 'cover': Scale the image uniformly (maintain the image's aspect ratio)
* so that both dimensions (width and height) of the image will be equal
* to or larger than the corresponding dimension of the view (minus padding).
*
* 'contain': Scale the image uniformly (maintain the image's aspect ratio)
* so that both dimensions (width and height) of the image will be equal to
* or less than the corresponding dimension of the view (minus padding).
*
* 'stretch': Scale width and height independently, This may change the
* aspect ratio of the src.
*
* 'center': Scale the image down so that it is completely visible,
* if bigger than the area of the view.
* The image will not be scaled up.
*/
resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch', 'center']),
},

statics: {
@@ -142,9 +177,36 @@ var Image = React.createClass({
* Prefetches a remote image for later use by downloading it to the disk
* cache
*/
prefetch(url: string) {
return ImageLoader.prefetchImage(url);
prefetch(url: string, callback: ?Function) {
const requestId = generateRequestId();
callback && callback(requestId);
return ImageLoader.prefetchImage(url, requestId);
},

/**
* Abort prefetch request
*/
abortPrefetch(requestId: number) {
ImageLoader.abortRequest(requestId);
},

/**
* Perform cache interrogation.
*
* @param urls the list of image URLs to check the cache for.
* @return a mapping from url to cache status, such as "disk" or "memory". If a requested URL is
* not in the mapping, it means it's not in the cache.
*/
async queryCache(urls: Array<string>): Promise<Map<string, 'memory' | 'disk'>> {
return await ImageLoader.queryCache(urls);
},

/**
* Resolves an asset reference into an object which has the properties `uri`, `width`,
* and `height`. The input may either be a number (opaque type returned by
* require('./foo.png')) or an `ImageSource` like { uri: '<http location || file path>' }
*/
resolveAssetSource: resolveAssetSource,
},

mixins: [NativeMethodsMixin],
@@ -212,28 +274,31 @@ var Image = React.createClass({
sources = source;
}

const {onLoadStart, onLoad, onLoadEnd} = this.props;
const {onLoadStart, onLoad, onLoadEnd, onError} = this.props;
const nativeProps = merge(this.props, {
style,
shouldNotifyLoadEvents: !!(onLoadStart || onLoad || onLoadEnd),
shouldNotifyLoadEvents: !!(onLoadStart || onLoad || onLoadEnd || onError),
src: sources,
loadingIndicatorSrc: loadingIndicatorSource ? loadingIndicatorSource.uri : null,
});

if (nativeProps.children) {
// TODO(6033040): Consider implementing this as a separate native component
const containerStyle = filterObject(style, (val, key) => !ImageSpecificStyleKeys.has(key));
const imageStyle = filterObject(style, (val, key) => ImageSpecificStyleKeys.has(key));
const imageProps = merge(nativeProps, {
style: styles.absoluteImage,
style: [imageStyle, styles.absoluteImage],
children: undefined,
});

return (
<View style={nativeProps.style}>
<View style={containerStyle}>
<RKImage {...imageProps}/>
{this.props.children}
</View>
);
} else {
return <RKImage {...nativeProps}/>;
return <RKImage {...nativeProps}/>;
}
}
return null;
@@ -257,9 +322,6 @@ var cfg = {
nativeOnly: {
src: true,
loadingIndicatorSrc: true,
defaultImageSrc: true,
imageTag: true,
progressHandlerRegistered: true,
shouldNotifyLoadEvents: true,
},
};
41 changes: 38 additions & 3 deletions ReactWindows/ReactNative/Modules/Image/ImageLoaderModule.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.Toolkit.Uwp.UI;
using Newtonsoft.Json.Linq;
using ReactNative.Bridge;
using ReactNative.Modules.Network;
using System;
using System.Reactive.Linq;
using Windows.UI.Xaml.Media.Imaging;
@@ -13,6 +14,8 @@ class ImageLoaderModule : NativeModuleBase
private const string ErrorPrefetchFailure = "E_PREFETCH_FAILURE";
private const string ErrorGetSizeFailure = "E_GET_SIZE_FAILURE";

private readonly TaskCancellationManager<int> _prefetchRequests = new TaskCancellationManager<int>();

public override string Name
{
get
@@ -22,7 +25,7 @@ public override string Name
}

[ReactMethod]
public void prefetchImage(string uriString, IPromise promise)
public void prefetchImage(string uriString, int requestId, IPromise promise)
{
if (string.IsNullOrEmpty(uriString))
{
@@ -34,9 +37,13 @@ public void prefetchImage(string uriString, IPromise promise)
{
try
{
// TODO: enable prefetch cancellation
var uri = new Uri(uriString);
await ImageCache.Instance.PreCacheAsync(uri, true, true).ConfigureAwait(false);

await _prefetchRequests.AddAndInvokeAsync(
requestId,
async token => await ImageCache.Instance.PreCacheAsync(uri, true, true, token).ConfigureAwait(false))
.ConfigureAwait(false);

promise.Resolve(true);
}
catch (Exception ex)
@@ -46,6 +53,12 @@ public void prefetchImage(string uriString, IPromise promise)
});
}

[ReactMethod]
public void abortRequest(int requestId)
{
_prefetchRequests.Cancel(requestId);
}

[ReactMethod]
public void getSize(string uriString, IPromise promise)
{
@@ -99,5 +112,27 @@ public void getSize(string uriString, IPromise promise)
}
});
}

[ReactMethod]
public async void queryCache(string[] urls, IPromise promise)
{
// TODO: support query for items in memory
var result = new JObject();
foreach (var url in urls)
{
var file = await ImageCache.Instance.GetFileFromCacheAsync(new Uri(url));
if (file != null)
{
result.Add(url, "disk");
}
}

promise.Resolve(result);
}

public override void OnReactInstanceDispose()
{
_prefetchRequests.CancelAllTasks();
}
}
}
2 changes: 1 addition & 1 deletion ReactWindows/ReactNative/project.json
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
"dependencies": {
"Facebook.Yoga": "1.0.1-pre",
"Microsoft.NETCore.UniversalWindowsPlatform": "5.2.2",
"Microsoft.Toolkit.Uwp.UI": "1.2.0",
"Microsoft.Toolkit.Uwp.UI": "1.3.1",
"Newtonsoft.Json": "9.0.1",
"PCLStorage": "1.0.2",
"System.Reactive": "3.0.0",