mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-22 16:33:29 +00:00
NSURLHandle implementation
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@7580 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
e2bf6a6571
commit
47eb44d12d
3 changed files with 387 additions and 205 deletions
|
@ -1,3 +1,9 @@
|
|||
2000-09-21 Richard Frith-Macdonald <rfm@gnu.org>
|
||||
|
||||
* Headers/gnustep/base/NSURLHandle.h: tidy up.
|
||||
* Source/NSURLHandle.m: Implement class and add simple implementation
|
||||
of a concrete subclass for handling file URLs.
|
||||
|
||||
2000-09-20 Richard Frith-Macdonald <rfm@gnu.org>
|
||||
|
||||
* Headers/gnustep/base/GSXML.h: Added ([-parser:]) and removed
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
#ifndef _NSURLHandle_h__
|
||||
#define _NSURLHandle_h__
|
||||
|
||||
@class NSData;
|
||||
@class NSString;
|
||||
@class NSMutableArray;
|
||||
@class NSMutableData;
|
||||
@class NSURLHandle;
|
||||
@class NSURL;
|
||||
|
||||
|
@ -57,48 +61,40 @@ typedef enum
|
|||
//=============================================================================
|
||||
@interface NSURLHandle: NSObject
|
||||
{
|
||||
NSMutableData *_data;
|
||||
NSMutableArray *_clients;
|
||||
id _data;
|
||||
NSString *_failure;
|
||||
NSURLHandleStatus _status;
|
||||
}
|
||||
|
||||
+ (NSURLHandle*) cachedHandleForURL: (NSURL*)url;
|
||||
+ (BOOL) canInitWithURL: (NSURL*)url;
|
||||
+ (void) registerURLHandleClass: (Class)urlHandleSubclass;
|
||||
+ (Class) URLHandleClassForURL: (NSURL*)url;
|
||||
|
||||
- (id) initWithURL: (NSURL*)url
|
||||
cached: (BOOL)cached;
|
||||
|
||||
- (NSURLHandleStatus) status;
|
||||
- (NSString*) failureReason;
|
||||
|
||||
- (void) addClient: (id <NSURLHandleClient>)client;
|
||||
- (void) removeClient: (id <NSURLHandleClient>)client;
|
||||
|
||||
- (void) loadInBackground;
|
||||
- (void) cancelLoadInBackground;
|
||||
|
||||
- (NSData*) resourceData;
|
||||
- (NSData*) availableResourceData;
|
||||
|
||||
- (void) flushCachedData;
|
||||
|
||||
- (void) backgroundLoadDidFailWithReason: (NSString*)reason;
|
||||
- (void) beginLoadInBackground;
|
||||
- (void) cancelLoadInBackground;
|
||||
- (void) didLoadBytes: (NSData*)newData
|
||||
loadComplete: (BOOL)loadComplete;
|
||||
|
||||
|
||||
+ (BOOL) canInitWithURL: (NSURL*)url;
|
||||
+ (NSURLHandle*) cachedHandleForURL: (NSURL*)url;
|
||||
|
||||
- (void) endLoadInBackground;
|
||||
- (NSString*) failureReason;
|
||||
- (void) flushCachedData;
|
||||
- (id) initWithURL: (NSURL*)url
|
||||
cached: (BOOL)cached;
|
||||
- (void) loadInBackground;
|
||||
- (NSData*) loadInForeground;
|
||||
- (id) propertyForKey: (NSString*)propertyKey;
|
||||
- (id) propertyForKeyIfAvailable: (NSString*)propertyKey;
|
||||
- (void) removeClient: (id <NSURLHandleClient>)client;
|
||||
- (NSData*) resourceData;
|
||||
- (NSURLHandleStatus) status;
|
||||
- (BOOL) writeData: (NSData*)data;
|
||||
- (BOOL) writeProperty: (id)propertyValue
|
||||
forKey: (NSString*)propertyKey;
|
||||
- (BOOL) writeData: (NSData*)data;
|
||||
|
||||
- (NSData*) loadInForeground;
|
||||
- (void) beginLoadInBackground;
|
||||
- (void) endLoadInBackground;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
Copyright (C) 1999 Free Software Foundation, Inc.
|
||||
|
||||
Written by: Manuel Guesdon <mguesdon@sbuilders.com>
|
||||
Date: Jan 1999
|
||||
Date: Jan 1999
|
||||
Update: Richard Frith-Macdonald <rfm@gnu.org>
|
||||
Date: Sep 2000
|
||||
|
||||
This file is part of the GNUstep Library.
|
||||
|
||||
|
@ -21,15 +23,11 @@
|
|||
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
Note from Manuel Guesdon:
|
||||
* functions are not implemented. If someone has documentation or ideas on
|
||||
how it should work...
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <base/behavior.h>
|
||||
#include <Foundation/NSArray.h>
|
||||
#include <Foundation/NSData.h>
|
||||
#include <Foundation/NSEnumerator.h>
|
||||
#include <Foundation/NSString.h>
|
||||
#include <Foundation/NSException.h>
|
||||
#include <Foundation/NSConcreteNumber.h>
|
||||
|
@ -37,39 +35,71 @@ how it should work...
|
|||
#include <Foundation/NSURL.h>
|
||||
#include <Foundation/NSMapTable.h>
|
||||
|
||||
//=============================================================================
|
||||
@class GSFileURLHandle;
|
||||
|
||||
@implementation NSURLHandle
|
||||
|
||||
static NSMapTable *cache = 0;
|
||||
static NSMutableArray *registry = nil;
|
||||
|
||||
+ (NSURLHandle*) cachedHandleForURL: (NSURL*)url
|
||||
{
|
||||
/*
|
||||
* Each subclass is supposed to do its own caching, so we must
|
||||
* find the correct subclass and ask it for its cached handle.
|
||||
*/
|
||||
if (self == [NSURLHandle class])
|
||||
{
|
||||
Class c = [self URLHandleClassForURL: url];
|
||||
|
||||
return [c cachedHandleForURL: url];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL) canInitWithURL: (NSURL*)url
|
||||
{
|
||||
/*
|
||||
* The semi-abstract base class can't handle ANY scheme
|
||||
*/
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
if (self == [NSURLHandle class])
|
||||
{
|
||||
cache = NSCreateMapTable(NSObjectMapKeyCallBacks,
|
||||
NSObjectMapValueCallBacks, 0);
|
||||
registry = [NSMutableArray new];
|
||||
[self registerURLHandleClass: [GSFileURLHandle class]];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void) registerURLHandleClass: (Class)_urlHandleSubclass
|
||||
+ (void) registerURLHandleClass: (Class)urlHandleSubclass
|
||||
{
|
||||
if ([registry indexOfObjectIdenticalTo: _urlHandleSubclass] == NSNotFound)
|
||||
{
|
||||
[registry addObject: _urlHandleSubclass];
|
||||
}
|
||||
/*
|
||||
* Maintain a registry of classes that handle various schemes
|
||||
* Re-adding a class moves it to the end of the registry - so it will
|
||||
* be used in preference to any class added earlier.
|
||||
*/
|
||||
[registry removeObjectIdenticalTo: urlHandleSubclass];
|
||||
[registry addObject: urlHandleSubclass];
|
||||
}
|
||||
|
||||
+ (Class) URLHandleClassForURL: (NSURL*)_url
|
||||
+ (Class) URLHandleClassForURL: (NSURL*)url
|
||||
{
|
||||
unsigned count = [registry count];
|
||||
|
||||
/*
|
||||
* Find a class to handle the URL, try most recently registered first.
|
||||
*/
|
||||
while (count-- > 0)
|
||||
{
|
||||
id found = [registry objectAtIndex: count];
|
||||
|
||||
if ([found canInitWithURL: _url] == YES)
|
||||
if ([found canInitWithURL: url] == YES)
|
||||
{
|
||||
return (Class)found;
|
||||
}
|
||||
|
@ -77,185 +107,335 @@ static NSMutableArray *registry = nil;
|
|||
return 0;
|
||||
}
|
||||
|
||||
- (id) initWithURL: (NSURL*)_url
|
||||
cached: (BOOL)_cached
|
||||
- (void) addClient: (id <NSURLHandleClient>)client
|
||||
{
|
||||
Class concreteSubclass;
|
||||
NSURLHandle *instance;
|
||||
|
||||
if (_cached == YES)
|
||||
{
|
||||
instance = (id)NSMapGet(cache, (void*)_url);
|
||||
if (instance != nil)
|
||||
{
|
||||
RELEASE(self);
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
concreteSubclass = [NSURLHandle URLHandleClassForURL: _url];
|
||||
if (concreteSubclass == 0)
|
||||
{
|
||||
NSLog(@"Attempt to init NSURLHandle with unsupported URL schema");
|
||||
RELEASE(self);
|
||||
}
|
||||
RELEASE(self);
|
||||
instance = [concreteSubclass alloc];
|
||||
instance = [instance initWithURL: _url cached: _cached];
|
||||
if (instance != nil)
|
||||
{
|
||||
NSMapInsert(cache, (void*)_url, (void*)instance);
|
||||
}
|
||||
return instance;
|
||||
[_clients addObject: client];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (NSURLHandleStatus) status
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
return (NSURLHandleStatus)0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (NSString*) failureReason
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (void) addClient: (id <NSURLHandleClient>)_client
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (void) removeClient: (id <NSURLHandleClient>)_client
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (void) loadInBackground
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (void) cancelLoadInBackground
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (NSData*) resourceData
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (NSData*) availableResourceData
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
return nil;
|
||||
return AUTORELEASE([_data copy]);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (void) flushCachedData
|
||||
{
|
||||
NSResetMapTable(cache);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (void) backgroundLoadDidFailWithReason: (NSString*)reason
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
NSEnumerator *enumerator = [_clients objectEnumerator];
|
||||
id <NSURLHandleClient> client;
|
||||
|
||||
_status = NSURLHandleLoadFailed;
|
||||
[_data setLength: 0];
|
||||
ASSIGNCOPY(_failure, reason);
|
||||
|
||||
while ((client = [enumerator nextObject]) != nil)
|
||||
{
|
||||
[client URLHandle: self resourceDidFailLoadingWithReason: _failure];
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (void) didLoadBytes: (NSData*)newData
|
||||
loadComplete: (BOOL)_loadComplete
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
+ (BOOL) canInitWithURL: (NSURL*)_url
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
return NO;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
+ (NSURLHandle*) cachedHandleForURL: (NSURL*)_url
|
||||
{
|
||||
return (NSURLHandle*) NSMapGet(cache, (void*)_url);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (id) propertyForKey: (NSString*)propertyKey
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (id) propertyForKeyIfAvailable: (NSString*)propertyKey
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (BOOL) writeProperty: (id)propertyValue
|
||||
forKey: (NSString*)propertyKey
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
return NO;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (BOOL) writeData: (NSData*)data
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
return NO;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (NSData*) loadInForeground
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (void) beginLoadInBackground
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
_status = NSURLHandleLoadInProgress;
|
||||
[_data setLength: 0];
|
||||
[_clients makeObjectsPerformSelector:
|
||||
@selector(URLHandleResourceDidBeginLoading:)
|
||||
withObject: self];
|
||||
}
|
||||
|
||||
- (void) cancelLoadInBackground
|
||||
{
|
||||
_status = NSURLHandleNotLoaded;
|
||||
[_data setLength: 0];
|
||||
[_clients makeObjectsPerformSelector:
|
||||
@selector(URLHandleResourceDidCancelLoading:)
|
||||
withObject: self];
|
||||
[self endLoadInBackground];
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(_data);
|
||||
RELEASE(_failure);
|
||||
RELEASE(_clients);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
/*
|
||||
* Mathod called by subclasses during process of loading a resource.
|
||||
* The base class maintains a copy of the data being read in and
|
||||
* accumulates separate parts of the data.
|
||||
*/
|
||||
- (void) didLoadBytes: (NSData*)newData
|
||||
loadComplete: (BOOL)loadComplete
|
||||
{
|
||||
NSEnumerator *enumerator;
|
||||
id <NSURLHandleClient> client;
|
||||
|
||||
/*
|
||||
* Let clients know we are starting loading (unless this has already been
|
||||
* done).
|
||||
*/
|
||||
if (_status != NSURLHandleLoadInProgress)
|
||||
{
|
||||
_status = NSURLHandleLoadInProgress;
|
||||
[_data setLength: 0];
|
||||
[_clients makeObjectsPerformSelector:
|
||||
@selector(URLHandleResourceDidBeginLoading:)
|
||||
withObject: self];
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have been given nil data, there must have been a failure!
|
||||
*/
|
||||
if (newData == nil)
|
||||
{
|
||||
[self backgroundLoadDidFailWithReason: @"nil data"];
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Let clients know we have read some data.
|
||||
*/
|
||||
enumerator = [_clients objectEnumerator];
|
||||
while ((client = [enumerator nextObject]) != nil)
|
||||
{
|
||||
[client URLHandle: self resourceDataDidBecomeAvailable: newData];
|
||||
}
|
||||
|
||||
/*
|
||||
* Accumulate data in cache.
|
||||
*/
|
||||
[_data appendData: newData];
|
||||
|
||||
if (loadComplete == YES)
|
||||
{
|
||||
/*
|
||||
* Let clients know we have finished loading.
|
||||
*/
|
||||
_status = NSURLHandleLoadSucceeded;
|
||||
[_clients makeObjectsPerformSelector:
|
||||
@selector(URLHandleResourceDidFinishLoading:)
|
||||
withObject: self];
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
- (void) endLoadInBackground
|
||||
{
|
||||
//FIXME
|
||||
[self notImplemented: _cmd];
|
||||
_status = NSURLHandleNotLoaded;
|
||||
[_data setLength: 0];
|
||||
}
|
||||
|
||||
- (NSString*) failureReason
|
||||
{
|
||||
if (_status == NSURLHandleLoadFailed)
|
||||
return _failure;
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void) flushCachedData
|
||||
{
|
||||
[_data setLength: 0];
|
||||
}
|
||||
|
||||
- (id) init
|
||||
{
|
||||
_status = NSURLHandleNotLoaded;
|
||||
_clients = [NSMutableArray new];
|
||||
_data = [NSMutableData new];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) initWithURL: (NSURL*)url
|
||||
cached: (BOOL)cached
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void) loadInBackground
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
}
|
||||
|
||||
- (NSData*) loadInForeground
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id) propertyForKey: (NSString*)propertyKey
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id) propertyForKeyIfAvailable: (NSString*)propertyKey
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void) removeClient: (id <NSURLHandleClient>)client
|
||||
{
|
||||
[_clients removeObjectIdenticalTo: client];
|
||||
}
|
||||
|
||||
- (NSData*) resourceData
|
||||
{
|
||||
if (_status == NSURLHandleLoadSucceeded)
|
||||
{
|
||||
return [self availableResourceData];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSData *d = [self loadInForeground];
|
||||
|
||||
if (d != nil)
|
||||
{
|
||||
[_data setData: d];
|
||||
}
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSURLHandleStatus) status
|
||||
{
|
||||
return _status;
|
||||
}
|
||||
|
||||
- (BOOL) writeData: (NSData*)data
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL) writeProperty: (id)propertyValue
|
||||
forKey: (NSString*)propertyKey
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface GSFileURLHandle : NSURLHandle
|
||||
{
|
||||
NSString *_path;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation GSFileURLHandle
|
||||
|
||||
static NSMutableDictionary *fileCache = nil;
|
||||
|
||||
+ (NSURLHandle*) cachedHandleForURL: (NSURL*)url
|
||||
{
|
||||
NSURLHandle *obj = nil;
|
||||
|
||||
if ([url isFileURL] == YES)
|
||||
{
|
||||
NSString *path = [url path];
|
||||
|
||||
path = [path stringByStandardizingPath];
|
||||
obj = [fileCache objectForKey: path];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
+ (BOOL) canInitWithURL: (NSURL*)url
|
||||
{
|
||||
if ([url isFileURL] == YES)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
fileCache = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(_path);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id) initWithURL: (NSURL*)url
|
||||
cached: (BOOL)cached
|
||||
{
|
||||
NSString *path;
|
||||
|
||||
if ([url isFileURL] == NO)
|
||||
{
|
||||
NSLog(@"Attempt to init GSFileURLHandle with bad URL");
|
||||
RELEASE(self);
|
||||
return nil;
|
||||
}
|
||||
path = [url path];
|
||||
path = [path stringByStandardizingPath];
|
||||
|
||||
if (cached == YES)
|
||||
{
|
||||
id obj;
|
||||
|
||||
obj = [fileCache objectForKey: path];
|
||||
if (obj != nil)
|
||||
{
|
||||
RELEASE(self);
|
||||
self = RETAIN(obj);
|
||||
return self;
|
||||
}
|
||||
}
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
_path = [path copy];
|
||||
if (cached == YES)
|
||||
{
|
||||
[fileCache setObject: self forKey: _path];
|
||||
RELEASE(self);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) loadInBackground
|
||||
{
|
||||
[self loadInForeground];
|
||||
}
|
||||
|
||||
- (NSData*) loadInForeground
|
||||
{
|
||||
NSData *d = [NSData dataWithContentsOfFile: _path];
|
||||
|
||||
[self didLoadBytes: d loadComplete: YES];
|
||||
return d;
|
||||
}
|
||||
|
||||
- (id) propertyForKey: (NSString*)propertyKey
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id) propertyForKeyIfAvailable: (NSString*)propertyKey
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL) writeData: (NSData*)data
|
||||
{
|
||||
/* FIXME */
|
||||
[self notImplemented: _cmd];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL) writeProperty: (id)propertyValue
|
||||
forKey: (NSString*)propertyKey
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
|
Loading…
Reference in a new issue