Check if my app has a new version on AppStore
Solution 1
Here is a simple code snippet that lets you know if the current version is different
-(BOOL) needsUpdate{
NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString* appID = infoDictionary[@"CFBundleIdentifier"];
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://itunes.apple.com/lookup?bundleId=%@", appID]];
NSData* data = [NSData dataWithContentsOfURL:url];
NSDictionary* lookup = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if ([lookup[@"resultCount"] integerValue] == 1){
NSString* appStoreVersion = lookup[@"results"][0][@"version"];
NSString* currentVersion = infoDictionary[@"CFBundleShortVersionString"];
if (![appStoreVersion isEqualToString:currentVersion]){
NSLog(@"Need to update [%@ != %@]", appStoreVersion, currentVersion);
return YES;
}
}
return NO;
}
Note: Make sure that when you enter the new version in iTunes, this matches the version in the app you are releasing. If not then the above code will always return YES regardless if the user updates.
Solution 2
Swift 3 version:
func isUpdateAvailable() throws -> Bool {
guard let info = Bundle.main.infoDictionary,
let currentVersion = info["CFBundleShortVersionString"] as? String,
let identifier = info["CFBundleIdentifier"] as? String,
let url = URL(string: "https://itunes.apple.com/lookup?bundleId=\(identifier)") else {
throw VersionError.invalidBundleInfo
}
let data = try Data(contentsOf: url)
guard let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any] else {
throw VersionError.invalidResponse
}
if let result = (json["results"] as? [Any])?.first as? [String: Any], let version = result["version"] as? String {
return version != currentVersion
}
throw VersionError.invalidResponse
}
I think is better to throw an error instead of returning false, in this case I created a VersionError but it can be some other you define or NSError
enum VersionError: Error {
case invalidResponse, invalidBundleInfo
}
Also consider to call this function from another thread, if the connection is slow it can block the current thread.
DispatchQueue.global().async {
do {
let update = try self.isUpdateAvailable()
DispatchQueue.main.async {
// show alert
}
} catch {
print(error)
}
}
Update
Using URLSession:
Instead of using Data(contentsOf: url)
and block a thread, we can use URLSession
:
func isUpdateAvailable(completion: @escaping (Bool?, Error?) -> Void) throws -> URLSessionDataTask {
guard let info = Bundle.main.infoDictionary,
let currentVersion = info["CFBundleShortVersionString"] as? String,
let identifier = info["CFBundleIdentifier"] as? String,
let url = URL(string: "https://itunes.apple.com/lookup?bundleId=\(identifier)") else {
throw VersionError.invalidBundleInfo
}
Log.debug(currentVersion)
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let error = error { throw error }
guard let data = data else { throw VersionError.invalidResponse }
let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any]
guard let result = (json?["results"] as? [Any])?.first as? [String: Any], let version = result["version"] as? String else {
throw VersionError.invalidResponse
}
completion(version != currentVersion, nil)
} catch {
completion(nil, error)
}
}
task.resume()
return task
}
example:
_ = try? isUpdateAvailable { (update, error) in
if let error = error {
print(error)
} else if let update = update {
print(update)
}
}
Solution 3
Simplified a great answer posted on this thread. Using Swift 4
and Alamofire
.
import Alamofire
class VersionCheck {
public static let shared = VersionCheck()
func isUpdateAvailable(callback: @escaping (Bool)->Void) {
let bundleId = Bundle.main.infoDictionary!["CFBundleIdentifier"] as! String
Alamofire.request("https://itunes.apple.com/lookup?bundleId=\(bundleId)").responseJSON { response in
if let json = response.result.value as? NSDictionary, let results = json["results"] as? NSArray, let entry = results.firstObject as? NSDictionary, let versionStore = entry["version"] as? String, let versionLocal = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
let arrayStore = versionStore.split(separator: ".").compactMap { Int($0) }
let arrayLocal = versionLocal.split(separator: ".").compactMap { Int($0) }
if arrayLocal.count != arrayStore.count {
callback(true) // different versioning system
return
}
// check each segment of the version
for (localSegment, storeSegment) in zip(arrayLocal, arrayStore) {
if localSegment < storeSegment {
callback(true)
return
}
}
}
callback(false) // no new version or failed to fetch app store version
}
}
}
And then to use it:
VersionCheck.shared.isUpdateAvailable() { hasUpdates in
print("is update available: \(hasUpdates)")
}
Solution 4
Updated the swift 4 code from Anup Gupta
I have made some alterations to this code. Now the functions are called from a background queue, since the connection can be slow and therefore block the main thread.
I also made the CFBundleName optional, since the version presented had "CFBundleDisplayName" which didn't work probably in my version. So now if it's not present it won't crash but just won't display the App Name in the alert.
import UIKit
enum VersionError: Error {
case invalidBundleInfo, invalidResponse
}
class LookupResult: Decodable {
var results: [AppInfo]
}
class AppInfo: Decodable {
var version: String
var trackViewUrl: String
}
class AppUpdater: NSObject {
private override init() {}
static let shared = AppUpdater()
func showUpdate(withConfirmation: Bool) {
DispatchQueue.global().async {
self.checkVersion(force : !withConfirmation)
}
}
private func checkVersion(force: Bool) {
let info = Bundle.main.infoDictionary
if let currentVersion = info?["CFBundleShortVersionString"] as? String {
_ = getAppInfo { (info, error) in
if let appStoreAppVersion = info?.version{
if let error = error {
print("error getting app store version: ", error)
} else if appStoreAppVersion == currentVersion {
print("Already on the last app version: ",currentVersion)
} else {
print("Needs update: AppStore Version: \(appStoreAppVersion) > Current version: ",currentVersion)
DispatchQueue.main.async {
let topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
topController.showAppUpdateAlert(Version: (info?.version)!, Force: force, AppURL: (info?.trackViewUrl)!)
}
}
}
}
}
}
private func getAppInfo(completion: @escaping (AppInfo?, Error?) -> Void) -> URLSessionDataTask? {
guard let identifier = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String,
let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else {
DispatchQueue.main.async {
completion(nil, VersionError.invalidBundleInfo)
}
return nil
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let error = error { throw error }
guard let data = data else { throw VersionError.invalidResponse }
let result = try JSONDecoder().decode(LookupResult.self, from: data)
guard let info = result.results.first else { throw VersionError.invalidResponse }
completion(info, nil)
} catch {
completion(nil, error)
}
}
task.resume()
return task
}
}
extension UIViewController {
@objc fileprivate func showAppUpdateAlert( Version : String, Force: Bool, AppURL: String) {
let appName = Bundle.appName()
let alertTitle = "New Version"
let alertMessage = "\(appName) Version \(Version) is available on AppStore."
let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
if !Force {
let notNowButton = UIAlertAction(title: "Not Now", style: .default)
alertController.addAction(notNowButton)
}
let updateButton = UIAlertAction(title: "Update", style: .default) { (action:UIAlertAction) in
guard let url = URL(string: AppURL) else {
return
}
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}
alertController.addAction(updateButton)
self.present(alertController, animated: true, completion: nil)
}
}
extension Bundle {
static func appName() -> String {
guard let dictionary = Bundle.main.infoDictionary else {
return ""
}
if let version : String = dictionary["CFBundleName"] as? String {
return version
} else {
return ""
}
}
}
I make this call for also adding the confirmation button:
AppUpdater.shared.showUpdate(withConfirmation: true)
Or call it to be called like this to have the force update option on:
AppUpdater.shared.showUpdate(withConfirmation: false)
Solution 5
Since I was facing the same problem, I found the answer provided by Mario Hendricks. Unfornatelly when I tryed to aply his code on my project, XCode did complain about Casting problems saying "MDLMaterialProperty has no subscript members". His code was trying to set this MDLMaterial... as the type of the constant "lookupResult", making the casting to "Int" failing every single time. My solution was to provide a type annotation for my variable to NSDictionary to be clear about the kind of value I needed. With that, I could access the value "version" that I needed.
Obs: For this YOURBUNDLEID, you can get from your Xcode project.... "Targets > General > Identity > Bundle Identifier"
So here is the my code with some simplifications as well:
func appUpdateAvailable() -> Bool
{
let storeInfoURL: String = "http://itunes.apple.com/lookup?bundleId=YOURBUNDLEID"
var upgradeAvailable = false
// Get the main bundle of the app so that we can determine the app's version number
let bundle = NSBundle.mainBundle()
if let infoDictionary = bundle.infoDictionary {
// The URL for this app on the iTunes store uses the Apple ID for the This never changes, so it is a constant
let urlOnAppStore = NSURL(string: storeInfoURL)
if let dataInJSON = NSData(contentsOfURL: urlOnAppStore!) {
// Try to deserialize the JSON that we got
if let dict: NSDictionary = try? NSJSONSerialization.JSONObjectWithData(dataInJSON, options: NSJSONReadingOptions.AllowFragments) as! [String: AnyObject] {
if let results:NSArray = dict["results"] as? NSArray {
if let version = results[0].valueForKey("version") as? String {
// Get the version number of the current version installed on device
if let currentVersion = infoDictionary["CFBundleShortVersionString"] as? String {
// Check if they are the same. If not, an upgrade is available.
print("\(version)")
if version != currentVersion {
upgradeAvailable = true
}
}
}
}
}
}
}
return upgradeAvailable
}
All suggestions for improvement of this code are welcome!
user542584
Updated on April 22, 2022Comments
-
user542584 about 2 years
I would like to manually check if there are new updates for my app while the user is in it, and prompt him to download the new version. Can I do this by checking the version of my app in the app store - programatically?
-
Pangolin almost 13 yearsYou could put a random page on a web-server which only returns a string representation of the latest version. Download it and compare upon app startup and notify the user. (Quick and easy way)
-
user542584 almost 13 yearsthanks, but I was hoping for a better solution like some sort of API with which I can call the app store functionalities, like search for my app number and get the version data. Saves time to maintain a webserver just for this purpose, but thanks for the pointer anyway!
-
Andrew almost 13 yearsI do the same thing as the first comment. I wrote a plist with one entry: an
NSNumber
version number. Then I uploaded it to my website. The same website I use for my app support and app webpages, then inviewDidLoad
, I check the website for the version number there and I check the current version in my app. Then I have a premadealertView
that automatically prompts to update the app. I can provide code if you would like. -
user542584 almost 13 yearsthanks, I guess I should try that too..
-
Stefanos Christodoulides almost 3 yearsI have implemented a solution using Google Firebase. I use remoteConfig to hold a value of the required version and when the app opens I cross check the version of the app with the version which is set to the Firebase. If the version of the app is smaller than the version of the Firebase I show the user an alert. This way I can have on demand force update of the application.
-
Leo Dabus about 2 yearsBetter to use Swift native type
OperatingSystemVersion
Compare app versions after update using decimals like 2.5.2
-
-
Steve Moser about 12 yearsYou can check the App Store directly for Version number instead of hosting a plist file somewhere. Check out this answer: stackoverflow.com/a/6569307/142358
-
Nick Lockwood about 12 yearsiVersion now uses the app store version automatically - the Plist is optional if you want to specify different release notes to the ones on iTunes, but you don't need to use it.
-
S.J about 10 yearsvery good and correct solution, just little update regarding the url is itunes.apple.com/en/lookup?bundleId=xxxxxxxxxx
-
Roozbeh Zabihollahi about 10 yearsThanks, your comment applied
-
Sanjay Changani about 9 yearssuper solution I ever Found +1
-
Mobeen Afzal over 8 yearsThis solution is not considered to be best or Super as it has one FLAW in it. e.g. if you have live version 1.0 on store and you want to update new version you submitted 1.1 so if it approves it will work good but in the case if you see 1.1 has some bug or crash or apple rejected it then you need to update to 1.2 so after 1.0 your version on store is 1.2 in this way it always return TRUE and show notify even user downloads the latest version.
-
datinc over 8 years@MobeenAfzal, I think you miss understood the question and solution. The above solution compares the current version with the version on the store. If they do not match then it retunes YES, else it returns NO. No matter the history on the app store the above method will return YES if the current version is different then the app store version. Once the user updates... the current version is equal to the app store version. The above method should always return YES if the user's version is 1.0 and the app store version is 1.2.
-
Mobeen Afzal over 8 years@datinc yes, obviously it always return yes and that is what the problem is.The problem is you push the new version with CFBundleversion 1.7 from code and you have in itunes like 1.6 version. It means according to itunes your version is latest and according to code you already use 1.6 so you cannot upload it will be redundant binary and on itunes you have to made 1.6 because past was 1.5. so in this case even latest version is live but it always return YES YES and all the logic based on YES will always stay.
-
datinc over 8 years@MobeenAfzal I think I get what you are seeing. In code your version is 1.7, But in iTunes you uploaded the version as 1.6 so that your users don't know you skipped a version. Is that the case? If so then... what you need is a server (DropBox would do) to serve your apps version number and modify your code to access that endpoint. Let me know if this is what you are seeing and I will add a note of warning to the post.
-
Mobeen Afzal over 8 years@datinc yes I know for that i need to implement Proper version controlling.Your solution will work best if the versions will always be 1 time greater then the previous version. if the gap increases from 1 it will stuck to true always. :) Cheers.
-
datinc over 8 years@MobeenAfzal you your comment is misleading. If the version on the user's device is separated by any from the version on the appstore then the code will return YES as expected. Even if you release version 1.0 followed by version 1.111 it would still work perfectly.
-
Mobeen Afzal over 8 years@datinc I know it will return yes. Itunes version 1.1 Code version 1.3 It always Return TRUE, because bundle version will be 1.1 and in code it will be 1.3 it can never be synced and get true and all the logic on that functionality will always APPEAR even if no more version is available to download from appstore. I hope I it is clear now
-
Lukasz Czerwinski over 8 yearsWill this work for beta versions in Testflight? If not, is there any tool that will?
-
emotality over 8 yearsNo it will not, it only compares the current version with the latest version that is on the AppStore.
-
Itachi about 8 yearsI tried to open the destination url with a common bundld id in chrome for mac, it said "errorMessage":"Invalid value(s) for key(s): [country]", "queryParameters":{"output":"json", "callback":"A javascript function to handle your search results", "country":"ISO-2A country code", "limit":"The number of search results to return", "term":"A search string", "lang":"ISO-2A language code"}"
-
gasparuff almost 8 yearsActually it didn't work for me with the
/en/
subpath. After removing it, it worked -
iamthevoid over 7 yearsstoreInfoURL is the url of app in appstore?
-
George Asda over 7 years@Mario Hendricks this is not working in swift 3. It throws some errors. Can you please update for swift 3?
-
Nitesh Borad about 7 yearsWe should show update only when appstore version is greater than current version as follows. if ([appStoreVersion compare:currentVersion options:NSNumericSearch] == NSOrderedDescending) { NSLog(@"\n\nNeed to update. Appstore version %@ is greater than %@",appStoreVersion, currentVersion); }
-
uliwitness about 7 yearsThis answer makes its request synchronously. This means with a bad connection, your app could be unusable for minutes until the request returns.
-
uliwitness about 7 yearsThis answer makes its request synchronously. This means with a bad connection, your app could be unusable for minutes until the request returns.
-
uliwitness about 7 yearsThis answer makes its request synchronously. This means with a bad connection, your app could be unusable for minutes until the request returns.
-
uliwitness about 7 yearsThis answer makes its request synchronously. This means with a bad connection, your app could be unusable for minutes until the request returns.
-
uliwitness about 7 yearsThis answer makes its request synchronously. This means with a bad connection, your app could be unusable for minutes until the request returns.
-
uliwitness about 7 yearsThis answer makes its request synchronously. This means with a bad connection, your app could be unusable for minutes until the request returns.
-
uliwitness about 7 yearsThis answer makes its request synchronously. This means with a bad connection, your app could be unusable for minutes until the request returns.
-
uliwitness about 7 yearsThis answer makes its request synchronously. This means with a bad connection, your app could be unusable for minutes until the request returns.
-
uliwitness about 7 yearsThis code could use some improvements, but is a lot better than the other answers that send a synchronous request. Still, the way it does threading is bad style. I'll file issues on Github.
-
byJeevan about 7 yearsThanks for keeping this question alive :-)
-
juanjo about 7 yearsI disagree,
DispatchQueue.global()
gives you a background queue, the data is loaded in that queue and only goes back to the main queue when the data is loaded. -
uliwitness about 7 yearsWhoops. Somehow I overlooked that second code snippet. Sadly, it seems I can't remove the downvote until your answer is edited again :-( BTW - Given dataWithContentsOfURL: actually goes through NSURLConnection's synchronous calls, which in turn just start an async thread and block, it'd probably be less overhead to just use the asynchronous NSURLSession calls. They'd even call you back on the main thread once you're done.
-
Kiran Jadhav almost 7 years@juanjo,,,, not working for swift 3.0.1 , please can u upload updated for swift ???
-
juanjo almost 7 yearsIs working for me with swift 3.1, what is the error?
-
Ryan Heitner over 6 yearsNote if you are only listed in a specific store I have found that you need to add a country code to the URL - eg GB itunes.apple.com/(countryCode)/…)
-
Pramod over 6 yearssuper solution +1
-
leshow over 6 yearsNSURLSession works on background threads automatically unless we specify otherwise.
-
JAL over 6 yearsLinks to an existing answer are not answers. Additionally, links to libraries are also not answers unless you explicitly add how the link answers the question to your answer (add code examples, etc).
-
Jigar about 6 years@Yago Zardo please use compare function otherwise when user upload app.apple tested time display update alertview or apple reject your app
-
Jigar about 6 yearsplease use compare function otherwise when user upload app.apple tested time display update alertview or apple reject your app
-
Yago Zardo about 6 yearsHey @Jigar, thanks for the advice. I'm currently not using this method anymore on my app because now we are versioning everything in our server. Anyway, could you explain better what you said? I did not understand and it really looks a good thing to know. Thanks in advance.
-
Yago Zardo about 6 yearsThank you @uliwitness for the tip, it really helped me to improve my code in general to learn about asynchronous and synchronous requests.
-
jessi about 6 yearsWhere did you put this code? II see that you set LookupResult and AppInfo to decodable, but I don't see them saved anywhere. What am I missing here?
-
juanjo about 6 yearsYou declare the
LookupResult
andAppInfo
classes somewhere in your project, in a separate file preferably: They are used when you decode the response:JSONDecoder().decode(LookupResult.self, from: data)
and they contain the version string -
Anup Gupta about 6 yearsBased on your answer I create One file using your code Please check that iOS-Swift-ArgAppUpdater
-
Anup Gupta about 6 years@jessi please check My code on GitHub I posted there your solution
-
Anup Gupta almost 6 years@Rob Please Check GitHub Link github.com/anupgupta-arg/iOS-Swift-ArgAppUpdater
-
Joris Mans almost 6 yearsThis crashes when you have no internet connection. let data = try? Data(contentsOf: url!) will return nil, and in the next line you do data!
-
Kassem Itani almost 6 yearsthx @JorisMans I will update it for no internet connectivity crash
-
JAL almost 6 yearsDon't do this. Use
URLSession
. -
B3none over 5 yearsThat link is a gem!
-
adamjansch over 5 yearsLove that pyramid. (Take a look at using
guard
instead ofif
.) -
David Rector over 5 yearsAny ideas on how to test this? If it fails to work right, the only way to debug it is to somehow debug an older version than is in the app store.
-
David Rector over 5 yearsAh, never mind the question. I can simply change my local version to be "older".
-
Master AgentX about 5 yearsI'm impressed with your code @Vasco. Just a simple question, why you have used 'http' instead of https in that url?
-
budiDino almost 5 yearsproblem with using ourVersion != appVersion is that it triggers when the App Store Review team checks the new version of the app. We convert those version strings to numbers and then isNew = appVersion > ourVersion.
-
Northern Captain almost 5 years@budidino you are right, I just showed the common approach using Alamofire. How you interpret the version is totally dependent on your app and version structure.
-
technerd almost 5 yearsMy application is live on store but same api not returning version information. Response :
{ "resultCount":0, "results": [] }
-
mc_plectrum over 4 yearsThanks a lot for sharing this solution @Vasco! I like it :) Why do you not use: let config = URLSessionConfiguration.background(withIdentifier: "com.example.MyExample.background") for the URLSession to achieve the background request?
-
mc_plectrum over 4 yearsYou can also get rid of the force unwrap, as you check already if if let appStoreAppVersion = info?.version and same for the trackURL.
-
Muju over 4 years@juanjo your code is working. But I have one problem when the update is available I want to redirect it to loginVC How can I do that
-
juanjo over 4 yearsHi, that depends on the structure of your controllers, I recommend you to ask another question to solve that and change the code inside the isUpdateAvailable completion closure
-
Fernando Perez over 4 yearsI had to use with the /en/ itunes.apple.com/lookup?bundleId=xxxxxxx, thanks @gasparuff
-
Chaitu about 4 yearsJust adding a note to version comparision, I would prefer, let serverVersion = "2.7" let localVersion = "2.6.5" let isUpdateAvailable = serverVersion.compare(localVersion, options: .numeric) == .orderedDescending rather than replacing the . with empty.
-
Chaitu about 4 yearsJust adding a note to version comparision, I would prefer, let serverVersion = "2.7" let localVersion = "2.6.5" let isUpdateAvailable = serverVersion.compare(localVersion, options: .numeric) == .orderedDescending rather than comparing with equal
-
budiDino about 4 years@Chaitu thank you for the suggestion. I ended up rewriting the comparison part of the code
-
Zorayr almost 4 yearsCould we use this with Swift?
-
Zorayr almost 4 yearsThe project is now deprecated 😢
-
Itachi over 3 yearsActually it's not a numeric style version always, so it should expose the version comparison outside.
-
emotality over 3 years@Itachi it was 5.5 years ago :) Package is not even being maintained anymore..
-
Markv07 about 3 yearsI just tested this in swift 5. It works well. I am currious how to know .version is the version available from the App Store (Bundle.main.InfoDictionary)? or how to know the CFBundleVersionString is the current app plist version number? I can't make sense of apple documentation. It would be nice to know if there are other fields that could be used from the App Store, such as what is the description of changes in the new version. That would help the user know if they should update. But thats not in any plist so probably not available..
-
Genevios almost 3 yearsDo you know what happened if AppStore get info with old version of app? I already clean cache but no one result. in JSON file new version but Apple give old version by key [result][0][version]
-
Admin over 2 yearsPlease add further details to expand on your answer, such as working code or documentation citations.
-
Libor Zapletal over 2 yearsThere should be
return
after some callbacks. -
budiDino over 2 years@LiborZapletal thanks. Fixed the issue and also updated the code a bit
-
Laszlo over 2 yearsinstead of
_ =
you should always add@discardableResult
for the function. -
Awais Fayyaz over 2 yearsI am facing a very weird issue. If i use the http version of the URL, it is returning me previous version which was uploaded on the App Store. However, if i use the https version of the url, the version is correct. Does any one know what might be the reason ?
-
stackich over 2 yearsLooks like this code returns only first two numbers of app version, for example: If its a 1.2.3 version on the App Store, the code will return just 1.2. Is there a way to get the last number too? Tnx.
-
stackich over 2 yearspozz @budiDino. Looks like this code returns only first two numbers of app version, for example: If its a 1.2.3 version on the App Store, the code will return just 1.2. Is there a way to get the last number too? Tnx.
-
budiDino over 2 years@stackich did you try checking the "version" returned from the itunes API:
https://itunes.apple.com/lookup?bundleId=\(bundleId)
and also your local version:Bundle.main.infoDictionary?["CFBundleShortVersionString"]
? Not sure why any of those would return just first two segments if 3 segments exist :/ -
stackich over 2 years@budiDino my fault. I made a mistake putting http instead of https in iTunes url. The http works but somehow returns the previous version of my app which was in my case without 3 segments(only two). Seems like switching to https kinda removes cache and returns the latest app version. Thanks however :)
-
Aloha over 2 years@Laszlo thanks for the heads up