From 8dd7b185472322af2d1c054f1f5a933bdb4cb4ed Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Tue, 14 Jul 2015 16:40:54 +0200 Subject: [PATCH 01/15] Add more advanced search functionnality --- _locales/en/messages.json | 16 +++++ _locales/fr/messages.json | 12 ++++ popup/partials/tags.html | 25 +++++++- src/background/Helper.js | 8 +++ src/background/SpotifyService.js | 81 ++++++++++++++++++++++--- src/background/TagsService.js | 2 +- src/popup/css/popup.css | 32 ++++++++-- src/popup/js/controllers/TagsCtrl.js | 54 ++++++++++++++--- src/popup/js/services/SpotifyService.js | 22 +++++++ 9 files changed, 230 insertions(+), 22 deletions(-) create mode 100644 src/popup/js/services/SpotifyService.js diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 8ee2feb..91ddb9c 100755 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -95,6 +95,22 @@ } } }, + "searchResults": { + "message": "Search results", + "description": "" + }, + "searchResultsPleaseSelect": { + "message": "Please select the right track below", + "description": "" + }, + "saveSelectedTrack": { + "message": "Save the selected track", + "description": "" + }, + "searchResults": { + "message": "Search results", + "description": "" + }, "settings": { "message": "Settings", "description": "" diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index c3a83c9..656235e 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -95,6 +95,18 @@ } } }, + "searchResults": { + "message": "Résultats de recherche", + "description": "" + }, + "searchResultsPleaseSelect": { + "message": "Veuillez sélectionner le bon morceau ci-dessous.", + "description": "" + }, + "saveSelectedTrack": { + "message": "Sauvegarder ce choix", + "description": "" + }, "settings": { "message": "Paramètres", "description": "" diff --git a/popup/partials/tags.html b/popup/partials/tags.html index 9307587..04a043b 100644 --- a/popup/partials/tags.html +++ b/popup/partials/tags.html @@ -52,7 +52,7 @@

myTags

-
+
@@ -97,5 +97,28 @@

myTags

cancel

{{newSearch.error}}

+
\ No newline at end of file diff --git a/src/background/Helper.js b/src/background/Helper.js index bad87c7..26332e3 100644 --- a/src/background/Helper.js +++ b/src/background/Helper.js @@ -31,6 +31,14 @@ text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; + }, + + escapeRegExp: function(string) { + return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); + }, + + replaceAll: function(string, find, replace) { + return string.replace(new RegExp(Helper.escapeRegExp(find), 'g'), replace); } }; diff --git a/src/background/SpotifyService.js b/src/background/SpotifyService.js index 57d6d2c..d761309 100644 --- a/src/background/SpotifyService.js +++ b/src/background/SpotifyService.js @@ -9,10 +9,10 @@ genQuery: function(track, artist) { var reSpaces = new RegExp(' ', 'g'); - return 'track:'+ track.replace(reSpaces, '+') +' artist:'+ artist.replace('Feat. ', '').replace(reSpaces, '+'); + return 'track:'+ track.replace(reSpaces, '+') +' artist:'+ Helper.replaceAll(artist, 'Feat. ', '').replace(reSpaces, '+'); }, - // Get current user and playlist ir cache or on Spotify + // Get current user and playlist in cache or on Spotify getUserAndPlaylist: function(callback) { Spotify.data.get(['userId', 'playlistId'], function(items) { var userId = items.userId; @@ -262,7 +262,7 @@ }, // Find a track on Spotify - findTrack: function(query, callback) { + findTrack: function(query, trackName, artist, callback) { Logger.info('[Spotify] Searching for track "'+ query +'"...'); Spotify.call({ @@ -271,17 +271,84 @@ params: { q: query, type: 'track', - limit: 1 + limit: 20 } }, function(err, data) { if(err) { Logger.info('[Spotify] Error searching track "'+ query +'".'); Logger.error(err); return callback(err); } if(data.tracks.total === 0) { Logger.info('[Spotify] Track "'+ query +'" not found.'); return callback(new Error('Not found')); } - var track = data.tracks.items[0]; + var found = null; + for(var i = 0; i < data.tracks.items.length && !found; i++) { + var track = data.tracks.items[i]; - Logger.info('[Spotify] Track found "'+ track.id +'".'); + // Only mark as found if the track name is exactly the same + // TODO: should check for artists too + if(track.name == trackName) { + found = track; + } + } + + if(found) { + Logger.info('[Spotify] Track found "'+ found.id +'".'); + + return callback(null, found); + } else { + Logger.info('[Spotify] Track "'+ query +'" not found.'); + + return callback(new Error('Not found')); + } + }); + }, + + // Find tracks on Spotify + findTracks: function(query, callback) { + Logger.info('[Spotify] Searching tracks for "'+ query +'"...'); - callback(null, track); + Spotify.call({ + endpoint: '/v1/search', + method: 'GET', + params: { + q: query, + type: 'track', + limit: 20 + } + }, function(err, data) { + if(err) { Logger.info('[Spotify] Error searching tracks for "'+ query +'".'); Logger.error(err); return callback(err); } + if(data.tracks.total === 0) { Logger.info('[Spotify] No tracks found for query "'+ query +'".'); return callback(new Error('Not found')); } + + var tracks = []; + + // Loop over all tracks found + for(var i = 0; i < data.tracks.items.length; i++) { + var track = data.tracks.items[i]; + + // Make a string with the artists' names + var artist = ''; + if(track.artists.length > 0) { + artist = track.artists[0].name; + for(var j = 1; j < track.artists.length; j++) { + artist += ', '+ track.artists[j].name; + } + } + + // Create a more concise track object and add to list + tracks.push({ + name: track.name, + artist: artist, + image: track.album.images[track.album.images.length-1].url, + id: track.id + }); + } + + if(tracks.length > 0) { + Logger.info('[Spotify] '+ tracks.length +' tracks found.'); + + return callback(null, tracks); + } else { + Logger.info('[Spotify] No tracks found for query "'+ query +'".'); + + return callback(new Error('Not found')); + } }); }, diff --git a/src/background/TagsService.js b/src/background/TagsService.js index 8eeb387..7939911 100644 --- a/src/background/TagsService.js +++ b/src/background/TagsService.js @@ -16,7 +16,7 @@ // Search track on Spotify, if not already found if(tag.status < 3) { - Spotify.findTrack(tag.query, function(err, track) { + Spotify.findTrack(tag.query, tag.name, tag.artist, function(err, track) { if(err || !track) { tag.status = 2; } else { diff --git a/src/popup/css/popup.css b/src/popup/css/popup.css index 9508c61..5c726cc 100644 --- a/src/popup/css/popup.css +++ b/src/popup/css/popup.css @@ -93,6 +93,21 @@ https://github.com/yui/pure/blob/master/LICENSE.md padding:6px 8px; } + hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #ccc; + margin: 5px 0; + padding: 0; + } + + h3 { + font-weight: 500; + font-size: 16px; + margin-bottom: 0; + } + /* Animations */ @-webkit-keyframes enterAnimation { @@ -454,15 +469,16 @@ https://github.com/yui/pure/blob/master/LICENSE.md min-height:240px; } - .tags-list .tag { + .tag { + display:block; overflow:hidden; clear:both; - margin-bottom:7px; + padding-top:7px; border-bottom:1px solid #e0e0e0; padding-bottom:7px; } - .tags-list .tag.ng-enter { + .tag.ng-enter { opacity: 0; transform: translate(0, -50px); @@ -470,7 +486,7 @@ https://github.com/yui/pure/blob/master/LICENSE.md animation: enterAnimation 0.2s ease forwards; } - .tags-list .tag.ng-enter-stagger { + .tag.ng-enter-stagger { -webkit-animation-delay:50ms; animation-delay:50ms; @@ -537,6 +553,14 @@ https://github.com/yui/pure/blob/master/LICENSE.md font-weight:normal; } + .tag.search-result { + + } + + .tag.search-result:hover { + background-color:#efefef; + } + /* Settings */ .settings h3 { diff --git a/src/popup/js/controllers/TagsCtrl.js b/src/popup/js/controllers/TagsCtrl.js index c0d30e5..6f6a7b9 100644 --- a/src/popup/js/controllers/TagsCtrl.js +++ b/src/popup/js/controllers/TagsCtrl.js @@ -1,4 +1,4 @@ -angular.module('Shazify').controller('TagsCtrl', function($scope, $location, $interval, BackgroundService, PopupStorage, LoginService, TagsService) { +angular.module('Shazify').controller('TagsCtrl', function($scope, $location, $interval, BackgroundService, PopupStorage, LoginService, TagsService, SpotifyService) { $scope.login = LoginService; $scope.updateStatus = ''; @@ -9,21 +9,55 @@ angular.module('Shazify').controller('TagsCtrl', function($scope, $location, $in show: false, tag: null, error: null, + results: [], query: { artist: '', track: '' }, + selectedTrack: null, + send: function() { - TagsService.searchTag($scope.newSearch.query.track, $scope.newSearch.query.artist, $scope.newSearch.tag, function(err) { - if(err) { - $scope.newSearch.error = chrome.i18n.getMessage('noTrackFoundQuery'); - } else { - $scope.newSearch.error = null; - $scope.newSearch.tag = null; - $scope.newSearch.show = false; - } + SpotifyService.genQuery($scope.newSearch.query.track, $scope.newSearch.query.artist, function(query) { + SpotifyService.searchTracks(query, function(err, tracks) { + if(err) { + $scope.newSearch.error = chrome.i18n.getMessage('noTrackFoundQuery'); + } else { + $scope.newSearch.results = tracks; + $scope.newSearch.error = null; + + // We search on list the current selected track + if($scope.newSearch.tag.spotifyId) { + for(var i = 0; i < tracks.length; i++) { + if(tracks[i].id == $scope.newSearch.tag.spotifyId) { + $scope.newSearch.selectedTrack = tracks[i]; + } + } + } + } + }); }); }, + + selectTrack: function(track) { + if($scope.newSearch.selectedTrack == track) { + $scope.newSearch.selectedTrack = null; + } else { + $scope.newSearch.selectedTrack = track; + } + }, + + saveSelectedTrack: function() { + $scope.newSearch.tag.spotifyId = $scope.newSearch.selectedTrack.id; + $scope.newSearch.tag.status = 3; + $scope.newSearch.tag.image = $scope.newSearch.selectedTrack.image; + + // TODO: Remove old selected from playlist, add this one instead and save tags + + $scope.newSearch.error = null; + $scope.newSearch.tag = null; + $scope.newSearch.show = false; + }, + cancel: function() { $scope.newSearch.error = null; $scope.newSearch.tag = null; @@ -35,6 +69,8 @@ angular.module('Shazify').controller('TagsCtrl', function($scope, $location, $in $scope.newSearch.query.artist = tag.artist; $scope.newSearch.query.track = tag.name; $scope.newSearch.tag = tag; + $scope.newSearch.results = []; + $scope.newSearch.selectedTrack = null; $scope.newSearch.show = true; }; diff --git a/src/popup/js/services/SpotifyService.js b/src/popup/js/services/SpotifyService.js new file mode 100644 index 0000000..f762e74 --- /dev/null +++ b/src/popup/js/services/SpotifyService.js @@ -0,0 +1,22 @@ +angular.module('Shazify').factory('SpotifyService', function($timeout, $interval, BackgroundService, LoginService) { + var SpotifyService = { + searchTracks: function(query, callback) { + BackgroundService.Spotify.findTracks(query, function(err, tracks) { + $timeout(function() { + console.log(err, tracks); + callback(err, tracks); + }, 0); + }); + }, + + genQuery: function(trackName, artist, callback) { + var query = BackgroundService.Spotify.genQuery(trackName, artist); + + $timeout(function() { + callback(query); + }, 0); + } + }; + + return SpotifyService; +}); \ No newline at end of file From e11c0222a678d5faefc46f5d5adde0e3e30f5d8c Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Wed, 15 Jul 2015 15:59:26 +0200 Subject: [PATCH 02/15] Start adding audio player functionnality --- src/background/SpotifyService.js | 39 ++++++++++++++++++++++++++ src/popup/js/directives/audioPlayer.js | 8 ++++++ src/popup/js/services/AudioService.js | 21 ++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 src/popup/js/directives/audioPlayer.js create mode 100644 src/popup/js/services/AudioService.js diff --git a/src/background/SpotifyService.js b/src/background/SpotifyService.js index d761309..c19b904 100644 --- a/src/background/SpotifyService.js +++ b/src/background/SpotifyService.js @@ -203,6 +203,45 @@ }); }, + removeTracks: function(tracksIds, callback) { + if(tracksIds.length === 0) { + Logger.info('[Spotify] No tracks to remove from playlist.'); + return callback(); + } + + Spotify.getUserAndPlaylist(function(err, userId, playlistId) { + if(err) { return callback(err); } + + Logger.info('[Spotify] '+ tracksIds.length +' tracks to remove from playlist.'); + + var tracks = []; + tracksIds.forEach(function(id) { + tracks.push({ uri: 'spotify:track:'+ id }); + }); + + Spotify.call({ + method: 'DELETE', + endpoint: '/v1/users/'+ userId +'/playlists/'+ playlistId +'/tracks', + data: JSON.stringify({ tracks: tracks }) + }, function(err, data) { + if(err) { + Logger.error('[Spotify] Error removing tracks from playlist.'); + Logger.error(err); + + return callback(err); + } + + if(data.snapshot_id) { + return callback(); + } else { + Logger.error('[Spotify] No snapshot_id returned for tracks removal.'); + + return callback(new Error('[Spotify] No snapshot_id returned for tracks removal.')); + } + }); + }); + }, + // Private : called from addTracks, add an array of trackPaths to playlist. // Handle arrays bigger than 100 items (should be splitted in multiple requests for Spotify API) _addTracksPaths: function(tracksPaths, callback) { diff --git a/src/popup/js/directives/audioPlayer.js b/src/popup/js/directives/audioPlayer.js new file mode 100644 index 0000000..158adae --- /dev/null +++ b/src/popup/js/directives/audioPlayer.js @@ -0,0 +1,8 @@ +angular.module('Shazify').directive('audioPlayer', function(AudioService) { + return { + restrict: 'E', + link: function($scope, elem) { + + } + }; +}); \ No newline at end of file diff --git a/src/popup/js/services/AudioService.js b/src/popup/js/services/AudioService.js new file mode 100644 index 0000000..9049782 --- /dev/null +++ b/src/popup/js/services/AudioService.js @@ -0,0 +1,21 @@ +angular.module('Shazify').factory('AudioService', function($timeout) { + var AudioService = { + _audio: new Audio(), + + setSrc: function(src) { + AudioService._audio.src = src; + }, + + play: function(src) { + src = src || null; + + if(src) { + AudioService.setSrc(src); + } + + AudioService._audio.play(); + } + }; + + return AudioService; +}); \ No newline at end of file From b3b42684b7b9a63d248e73864c8acdff755c92a6 Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Tue, 15 Sep 2015 11:30:06 +0200 Subject: [PATCH 03/15] Continue dev --- _locales/en/messages.json | 6 ++-- _locales/fr/messages.json | 8 ++--- popup/partials/tags.html | 7 ++-- src/background/DbService.js | 51 ++++++++++++++++++++++++++++ src/popup/css/popup.css | 9 ++++- src/popup/js/controllers/TagsCtrl.js | 14 +++----- 6 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 src/background/DbService.js diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 91ddb9c..cd5d90e 100755 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -78,11 +78,11 @@ "description": "" }, "trackNotFound": { - "message": "Oops ! We cannot find this track on Spotify...", + "message": "Oops ! We cannot find this track on Spotify... Please check/correct the search query below and then select the right track from the results that will be displayed.", "description": "" }, - "searchQueryPrompt": { - "message": "Please help us by entering/updating the search query below.", + "changeTrackSearch": { + "message": "Not the right track found? You can change the search query below and then select the right track from the results that will be displayed.", "description": "" }, "noTrackFoundQuery": { diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 656235e..f464328 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -78,12 +78,12 @@ "description": "" }, "trackNotFound": { - "message": "Oops ! Nous ne trouvons pas ce morceau sur Spotify...", + "message": "Oops ! Nous ne trouvons pas ce morceau sur Spotify... Veuillez modifier la requête de recherche ci-dessous et sélectionner le bon morceau dans les résultats qui s'afficheront.", "description": "" }, - "searchQueryPrompt": { - "message": "Aidez-nous en entrant une nouvelle requête de recherche :", - "description": "" + "changeTrackSearch": { + "message": "Pas le bon morceau trouvé? Vous pouvez modifier la requête de recherche ci-dessous et sélectionner celui qui correspond le mieux.", + "description": "" }, "noTrackFoundQuery": { "message": "Aucun morceau trouvé... veuillez modifier les champs ci-dessus et réessayer.", diff --git a/popup/partials/tags.html b/popup/partials/tags.html index 04a043b..18f1c8d 100644 --- a/popup/partials/tags.html +++ b/popup/partials/tags.html @@ -86,9 +86,9 @@

myTags

{{tag.artist}}
-
\ No newline at end of file diff --git a/src/background/DbService.js b/src/background/DbService.js new file mode 100644 index 0000000..ac09e54 --- /dev/null +++ b/src/background/DbService.js @@ -0,0 +1,51 @@ +(function(StorageHelper, Logger, Spotify, Shazam){ + var Db = (function() { + var tDB = {}; + var datastore = null; + + /** + * Open a connection to the datastore. + */ + tDB.open = function(callback) { + // Database version. + var version = 1; + + // Open a connection to the datastore. + var request = indexedDB.open('todos', version); + + // Handle datastore upgrades. + request.onupgradeneeded = function(e) { + var db = e.target.result; + + e.target.transaction.onerror = tDB.onerror; + + // Delete the old datastore. + if (db.objectStoreNames.contains('todo')) { + db.deleteObjectStore('todo'); + } + + // Create a new datastore. + var store = db.createObjectStore('todo', { + keyPath: 'timestamp' + }); + }; + + // Handle successful datastore access. + request.onsuccess = function(e) { + // Get a reference to the DB. + datastore = e.target.result; + + // Execute the callback. + callback(); + }; + + // Handle errors when opening the datastore. + request.onerror = tDB.onerror; + }; + + // Export the tDB object. + return tDB; + }()); + + window.s2s.Db = Db; +})(window.s2s.StorageHelper, window.s2s.Logger, window.s2s.Spotify, window.s2s.Shazam); \ No newline at end of file diff --git a/src/popup/css/popup.css b/src/popup/css/popup.css index 5c726cc..dac5f07 100644 --- a/src/popup/css/popup.css +++ b/src/popup/css/popup.css @@ -238,7 +238,7 @@ https://github.com/yui/pure/blob/master/LICENSE.md } .modal input { - width: 270px; + width: 100%;/*270px*/ background-color: white; border: 1px solid #cccccc; padding: 6px 8px; @@ -561,6 +561,13 @@ https://github.com/yui/pure/blob/master/LICENSE.md background-color:#efefef; } + .new-search-modal { + bottom:20px; + top:20px; + overflow-y:auto; + overflow-x:hidden; + } + /* Settings */ .settings h3 { diff --git a/src/popup/js/controllers/TagsCtrl.js b/src/popup/js/controllers/TagsCtrl.js index 6f6a7b9..d9644f5 100644 --- a/src/popup/js/controllers/TagsCtrl.js +++ b/src/popup/js/controllers/TagsCtrl.js @@ -39,17 +39,9 @@ angular.module('Shazify').controller('TagsCtrl', function($scope, $location, $in }, selectTrack: function(track) { - if($scope.newSearch.selectedTrack == track) { - $scope.newSearch.selectedTrack = null; - } else { - $scope.newSearch.selectedTrack = track; - } - }, - - saveSelectedTrack: function() { - $scope.newSearch.tag.spotifyId = $scope.newSearch.selectedTrack.id; + $scope.newSearch.tag.spotifyId = track.id; $scope.newSearch.tag.status = 3; - $scope.newSearch.tag.image = $scope.newSearch.selectedTrack.image; + $scope.newSearch.tag.image = track.image; // TODO: Remove old selected from playlist, add this one instead and save tags @@ -72,6 +64,8 @@ angular.module('Shazify').controller('TagsCtrl', function($scope, $location, $in $scope.newSearch.results = []; $scope.newSearch.selectedTrack = null; $scope.newSearch.show = true; + + $scope.newSearch.send(); }; var updateStatus = function(){ From 45c3ec61309e6a8f931410f69af1e0ffb10e6313 Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Fri, 9 Dec 2016 13:12:54 +0100 Subject: [PATCH 04/15] Handle new Shazam login --- .gitignore | 1 + gruntfile.js | 11 +- package.json | 18 +- src/background/ChromeHelper.js | 8 +- src/background/DbService.js | 55 +- src/background/ShazamService.js | 102 +- src/background/lib/dexie.js | 4761 +++++++++++++++++++++++++++++++ 7 files changed, 4883 insertions(+), 73 deletions(-) create mode 100644 src/background/lib/dexie.js diff --git a/.gitignore b/.gitignore index 4d86e47..1f37a06 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ node_modules/ popup/popup.css popup/popup.js background/background.js +contentscripts/ dist/ build/ \ No newline at end of file diff --git a/gruntfile.js b/gruntfile.js index 0e9f204..0e58d27 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -4,7 +4,10 @@ module.exports = function(grunt) { pkg: grunt.file.readJSON('package.json'), archive_name: 'shazify-<%= pkg.version %>', jshint: { - globals: { angular: true }, + options: { + globals: { angular: true }, + ignores: 'src/background/lib/*' + }, all: ['gruntfile.js', 'src/popup/js/**/*.js', 'src/background/**/*.js'], gruntfile: ['gruntfile.js'] }, @@ -19,6 +22,9 @@ module.exports = function(grunt) { }, js: { files: { + 'contentscripts/shazamLocalStorage.js': [ + 'src/contentscripts/shazamLocalStorage.js' + ], 'popup/popup.js': [ 'src/popup/js/app.js', 'src/popup/js/**/*.js' @@ -29,6 +35,7 @@ module.exports = function(grunt) { 'src/background/Helper.js', 'src/background/ChromeHelper.js', 'src/background/CanvasIcon.js', + 'src/background/DbService.js', 'src/background/LoggerService.js', 'src/background/StorageHelper.js', 'src/background/SpotifyService.js', @@ -55,7 +62,7 @@ module.exports = function(grunt) { } }, js: { - files: ['src/popup/js/**/*.js', 'src/background/**/*.js'], + files: ['src/contentscripts/*.js', 'src/popup/js/**/*.js', 'src/background/**/*.js'], tasks: ['build:js'], options: { spawn: false, diff --git a/package.json b/package.json index 34c5c44..9459da5 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,14 @@ "private": true, "description": "Easily sync your Shazam tracks with a Spotify playlist", "devDependencies": { - "grunt": "*", - "grunt-cli": "*", - "grunt-contrib-concat": "*", - "grunt-contrib-jshint": "*", - "grunt-contrib-watch": "*", - "grunt-contrib-compress": "*", - "grunt-contrib-copy": "*", - "grunt-contrib-clean": "*" + "grunt": "^1.0.1", + "grunt-cli": "^1.2.0", + "grunt-contrib-clean": "^1.0.0", + "grunt-contrib-compress": "^1.3.0", + "grunt-contrib-concat": "^1.0.1", + "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-jshint": "^1.1.0", + "grunt-contrib-watch": "^1.0.0" }, "license": "MIT" -} \ No newline at end of file +} diff --git a/src/background/ChromeHelper.js b/src/background/ChromeHelper.js index 26477fe..270f72d 100644 --- a/src/background/ChromeHelper.js +++ b/src/background/ChromeHelper.js @@ -30,13 +30,17 @@ }, // Focus existing tab or create it - focusOrCreateTab: function(url) { + focusOrCreateTab: function(url, callback) { + callback = callback || function(){}; + ChromeHelper.findExistingTab(url, function(existing_tab) { if (existing_tab) { chrome.tabs.reload(existing_tab.id, {'bypassCache': true}); chrome.tabs.update(existing_tab.id, {'selected': true}); + + return callback(existing_tab); } else { - chrome.tabs.create({'url': url, 'selected': true}); + chrome.tabs.create({'url': url, 'selected': true}, callback); } }); }, diff --git a/src/background/DbService.js b/src/background/DbService.js index ac09e54..a36379b 100644 --- a/src/background/DbService.js +++ b/src/background/DbService.js @@ -1,51 +1,18 @@ -(function(StorageHelper, Logger, Spotify, Shazam){ +(function(Logger){ var Db = (function() { - var tDB = {}; - var datastore = null; + var db = new Dexie('Shazify'); - /** - * Open a connection to the datastore. - */ - tDB.open = function(callback) { - // Database version. - var version = 1; + /*db.version(1).stores({ + users: "++id, name, &username, *email, address.city", + relations: "++, userId1, userId2, [userId1+userId2], relation" + }); - // Open a connection to the datastore. - var request = indexedDB.open('todos', version); + db.open().catch(function (e) { + console.error("Open failed: " + e.stack); + });*/ - // Handle datastore upgrades. - request.onupgradeneeded = function(e) { - var db = e.target.result; - - e.target.transaction.onerror = tDB.onerror; - - // Delete the old datastore. - if (db.objectStoreNames.contains('todo')) { - db.deleteObjectStore('todo'); - } - - // Create a new datastore. - var store = db.createObjectStore('todo', { - keyPath: 'timestamp' - }); - }; - - // Handle successful datastore access. - request.onsuccess = function(e) { - // Get a reference to the DB. - datastore = e.target.result; - - // Execute the callback. - callback(); - }; - - // Handle errors when opening the datastore. - request.onerror = tDB.onerror; - }; - - // Export the tDB object. - return tDB; + return db; }()); window.s2s.Db = Db; -})(window.s2s.StorageHelper, window.s2s.Logger, window.s2s.Spotify, window.s2s.Shazam); \ No newline at end of file +})(window.s2s.Logger); \ No newline at end of file diff --git a/src/background/ShazamService.js b/src/background/ShazamService.js index bb4ee54..b8b9078 100644 --- a/src/background/ShazamService.js +++ b/src/background/ShazamService.js @@ -1,27 +1,97 @@ -(function(ChromeHelper, Logger){ +(function(ChromeHelper, Logger, StorageHelper){ + /* + Shazam service + + Handle login to Shazam, getting auth token (with a content script) and calling API to fetch tags + + */ var Shazam = { + // Storage for Shazam data (auth token, etc) + data: new StorageHelper('Shazam', 'sync'), // New storage, synced with other Chrome installs + + receivePageLocalStorage: function (pageLocalStorage) { + console.log('pageLocalStorage', pageLocalStorage); + }, + + getInid: function(tabId) { + listenerAdded = (typeof listenerAdded !== 'undefined') ? listenerAdded : false; + + Logger.info('[Shazam] Injecting content script to get "inid" from Local Storage...'); + + chrome.tabs.executeScript(tabId, { + file: 'contentscripts/shazamLocalStorage.js', + runAt: 'document_end' + }); + + function receiveMessage(request, sender, sendResponse) { + if(request.shazamLocalStorage && request.shazamLocalStorage.inid) { + Shazam.setAndCheckInid(request.shazamLocalStorage.inid, function(isFine) { + sendResponse({ isFine: isFine }); + + if(!isFine) { + Shazam.data.set({ 'inid': null }); + Logger.info('[Shazam] "inid" returned is not fine...'); + } else { + Logger.info('[Shazam] "inid" returned is fine!'); + chrome.runtime.onMessage.removeListener(receiveMessage); + } + }); + } + + // Let us use "sendResponse" asynchronously + return true; + } + + chrome.runtime.onMessage.addListener(receiveMessage); + }, + + setAndCheckInid: function(inid, callback) { + callback = callback || function(){}; + + Shazam.data.set({ + 'inid': inid + }, function() { + return Shazam.loginStatus(callback); + }); + }, + // Open the MyShazam login page openLogin: function() { Logger.info('[Shazam] Opening login page...'); - ChromeHelper.focusOrCreateTab('https://www.shazam.com/myshazam'); + ChromeHelper.focusOrCreateTab('https://www.shazam.com/myshazam', function(tab) { + // Will inject content script to Shazam page and wait for the inid to be available + // When available, will set and check it + Shazam.getInid(tab.id); + }); }, // Check current login status on MyShazam loginStatus: function(callback) { - $.get('https://www.shazam.com/fragment/myshazam') - .done(function() { - Logger.info('[Shazam] login status : logged.'); - callback(true); - }) - .fail(function(jqXHR, textStatus) { - if(jqXHR.status === 401) { - Logger.info('[Shazam] login status : not logged (401).'); - Logger.error(textStatus); - callback(false); - } else { + Shazam.data.get('inid', function(items) { + if(!items.inid) { + return callback(false); + } + + $.get('https://www.shazam.com/discovery/v4/fr/CH/web/-/tag/'+ items.inid +'?limit=20') + .done(function() { Logger.info('[Shazam] login status : logged.'); - callback(true); - } + return callback(true); + }) + .fail(function(jqXHR, textStatus) { + if(jqXHR.status === 401) { + Logger.info('[Shazam] login status : not logged (401).'); + Logger.error(textStatus); + return callback(false); + } else if(jqXHR.status === 456) { + Logger.info('[Shazam] login status : error (456).'); + Logger.error(textStatus); + return callback(false); + } + + Logger.info('[Shazam] login status : error.'); + Logger.error(textStatus); + return callback(false); + }); }); }, @@ -90,4 +160,4 @@ }; window.s2s.Shazam = Shazam; -})(window.s2s.ChromeHelper, window.s2s.Logger); \ No newline at end of file +})(window.s2s.ChromeHelper, window.s2s.Logger, window.s2s.StorageHelper); \ No newline at end of file diff --git a/src/background/lib/dexie.js b/src/background/lib/dexie.js new file mode 100644 index 0000000..d01cce4 --- /dev/null +++ b/src/background/lib/dexie.js @@ -0,0 +1,4761 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.Dexie = factory()); +}(this, (function () { 'use strict'; + +/* +* Dexie.js - a minimalistic wrapper for IndexedDB +* =============================================== +* +* By David Fahlander, david.fahlander@gmail.com +* +* Version 2.0.0-beta.6, Mon Nov 28 2016 +* www.dexie.com +* Apache License Version 2.0, January 2004, http://www.apache.org/licenses/ +*/ +var keys = Object.keys; +var isArray = Array.isArray; +var _global = typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : global; + +function extend(obj, extension) { + if (typeof extension !== 'object') return obj; + keys(extension).forEach(function (key) { + obj[key] = extension[key]; + }); + return obj; +} + +var getProto = Object.getPrototypeOf; +var _hasOwn = {}.hasOwnProperty; +function hasOwn(obj, prop) { + return _hasOwn.call(obj, prop); +} + +function props(proto, extension) { + if (typeof extension === 'function') extension = extension(getProto(proto)); + keys(extension).forEach(function (key) { + setProp(proto, key, extension[key]); + }); +} + +var defineProperty = Object.defineProperty; + +function setProp(obj, prop, functionOrGetSet, options) { + defineProperty(obj, prop, extend(functionOrGetSet && hasOwn(functionOrGetSet, "get") && typeof functionOrGetSet.get === 'function' ? { get: functionOrGetSet.get, set: functionOrGetSet.set, configurable: true } : { value: functionOrGetSet, configurable: true, writable: true }, options)); +} + +function derive(Child) { + return { + from: function (Parent) { + Child.prototype = Object.create(Parent.prototype); + setProp(Child.prototype, "constructor", Child); + return { + extend: props.bind(null, Child.prototype) + }; + } + }; +} + +var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; + +function getPropertyDescriptor(obj, prop) { + var pd = getOwnPropertyDescriptor(obj, prop), + proto; + return pd || (proto = getProto(obj)) && getPropertyDescriptor(proto, prop); +} + +var _slice = [].slice; +function slice(args, start, end) { + return _slice.call(args, start, end); +} + +function override(origFunc, overridedFactory) { + return overridedFactory(origFunc); +} + +function doFakeAutoComplete(fn) { + var to = setTimeout(fn, 1000); + clearTimeout(to); +} + +function assert(b) { + if (!b) throw new Error("Assertion Failed"); +} + +function asap(fn) { + if (_global.setImmediate) setImmediate(fn);else setTimeout(fn, 0); +} + + + +/** Generate an object (hash map) based on given array. + * @param extractor Function taking an array item and its index and returning an array of 2 items ([key, value]) to + * instert on the resulting object for each item in the array. If this function returns a falsy value, the + * current item wont affect the resulting object. + */ +function arrayToObject(array, extractor) { + return array.reduce(function (result, item, i) { + var nameAndValue = extractor(item, i); + if (nameAndValue) result[nameAndValue[0]] = nameAndValue[1]; + return result; + }, {}); +} + +function trycatcher(fn, reject) { + return function () { + try { + fn.apply(this, arguments); + } catch (e) { + reject(e); + } + }; +} + +function tryCatch(fn, onerror, args) { + try { + fn.apply(null, args); + } catch (ex) { + onerror && onerror(ex); + } +} + +function getByKeyPath(obj, keyPath) { + // http://www.w3.org/TR/IndexedDB/#steps-for-extracting-a-key-from-a-value-using-a-key-path + if (hasOwn(obj, keyPath)) return obj[keyPath]; // This line is moved from last to first for optimization purpose. + if (!keyPath) return obj; + if (typeof keyPath !== 'string') { + var rv = []; + for (var i = 0, l = keyPath.length; i < l; ++i) { + var val = getByKeyPath(obj, keyPath[i]); + rv.push(val); + } + return rv; + } + var period = keyPath.indexOf('.'); + if (period !== -1) { + var innerObj = obj[keyPath.substr(0, period)]; + return innerObj === undefined ? undefined : getByKeyPath(innerObj, keyPath.substr(period + 1)); + } + return undefined; +} + +function setByKeyPath(obj, keyPath, value) { + if (!obj || keyPath === undefined) return; + if ('isFrozen' in Object && Object.isFrozen(obj)) return; + if (typeof keyPath !== 'string' && 'length' in keyPath) { + assert(typeof value !== 'string' && 'length' in value); + for (var i = 0, l = keyPath.length; i < l; ++i) { + setByKeyPath(obj, keyPath[i], value[i]); + } + } else { + var period = keyPath.indexOf('.'); + if (period !== -1) { + var currentKeyPath = keyPath.substr(0, period); + var remainingKeyPath = keyPath.substr(period + 1); + if (remainingKeyPath === "") { + if (value === undefined) delete obj[currentKeyPath];else obj[currentKeyPath] = value; + } else { + var innerObj = obj[currentKeyPath]; + if (!innerObj) innerObj = obj[currentKeyPath] = {}; + setByKeyPath(innerObj, remainingKeyPath, value); + } + } else { + if (value === undefined) delete obj[keyPath];else obj[keyPath] = value; + } + } +} + +function delByKeyPath(obj, keyPath) { + if (typeof keyPath === 'string') setByKeyPath(obj, keyPath, undefined);else if ('length' in keyPath) [].map.call(keyPath, function (kp) { + setByKeyPath(obj, kp, undefined); + }); +} + +function shallowClone(obj) { + var rv = {}; + for (var m in obj) { + if (hasOwn(obj, m)) rv[m] = obj[m]; + } + return rv; +} + +var concat = [].concat; +function flatten(a) { + return concat.apply([], a); +} + +//https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm +var intrinsicTypes = "Boolean,String,Date,RegExp,Blob,File,FileList,ArrayBuffer,DataView,Uint8ClampedArray,ImageData,Map,Set".split(',').concat(flatten([8, 16, 32, 64].map(function (num) { + return ["Int", "Uint", "Float"].map(function (t) { + return t + num + "Array"; + }); +}))).filter(function (t) { + return _global[t]; +}).map(function (t) { + return _global[t]; +}); + +function deepClone(any) { + if (!any || typeof any !== 'object') return any; + var rv; + if (isArray(any)) { + rv = []; + for (var i = 0, l = any.length; i < l; ++i) { + rv.push(deepClone(any[i])); + } + } else if (intrinsicTypes.indexOf(any.constructor) >= 0) { + rv = any; + } else { + rv = any.constructor ? Object.create(any.constructor.prototype) : {}; + for (var prop in any) { + if (hasOwn(any, prop)) { + rv[prop] = deepClone(any[prop]); + } + } + } + return rv; +} + +function getObjectDiff(a, b, rv, prfx) { + // Compares objects a and b and produces a diff object. + rv = rv || {}; + prfx = prfx || ''; + keys(a).forEach(function (prop) { + if (!hasOwn(b, prop)) rv[prfx + prop] = undefined; // Property removed + else { + var ap = a[prop], + bp = b[prop]; + if (typeof ap === 'object' && typeof bp === 'object' && ap && bp && ap.constructor === bp.constructor) + // Same type of object but its properties may have changed + getObjectDiff(ap, bp, rv, prfx + prop + ".");else if (ap !== bp) rv[prfx + prop] = b[prop]; // Primitive value changed + } + }); + keys(b).forEach(function (prop) { + if (!hasOwn(a, prop)) { + rv[prfx + prop] = b[prop]; // Property added + } + }); + return rv; +} + +// If first argument is iterable or array-like, return it as an array +var iteratorSymbol = typeof Symbol !== 'undefined' && Symbol.iterator; +var getIteratorOf = iteratorSymbol ? function (x) { + var i; + return x != null && (i = x[iteratorSymbol]) && i.apply(x); +} : function () { + return null; +}; + +var NO_CHAR_ARRAY = {}; +// Takes one or several arguments and returns an array based on the following criteras: +// * If several arguments provided, return arguments converted to an array in a way that +// still allows javascript engine to optimize the code. +// * If single argument is an array, return a clone of it. +// * If this-pointer equals NO_CHAR_ARRAY, don't accept strings as valid iterables as a special +// case to the two bullets below. +// * If single argument is an iterable, convert it to an array and return the resulting array. +// * If single argument is array-like (has length of type number), convert it to an array. +function getArrayOf(arrayLike) { + var i, a, x, it; + if (arguments.length === 1) { + if (isArray(arrayLike)) return arrayLike.slice(); + if (this === NO_CHAR_ARRAY && typeof arrayLike === 'string') return [arrayLike]; + if (it = getIteratorOf(arrayLike)) { + a = []; + while (x = it.next(), !x.done) { + a.push(x.value); + }return a; + } + if (arrayLike == null) return [arrayLike]; + i = arrayLike.length; + if (typeof i === 'number') { + a = new Array(i); + while (i--) { + a[i] = arrayLike[i]; + }return a; + } + return [arrayLike]; + } + i = arguments.length; + a = new Array(i); + while (i--) { + a[i] = arguments[i]; + }return a; +} + +// By default, debug will be true only if platform is a web platform and its page is served from localhost. +// When debug = true, error's stacks will contain asyncronic long stacks. +var debug = typeof location !== 'undefined' && +// By default, use debug mode if served from localhost. +/^(http|https):\/\/(localhost|127\.0\.0\.1)/.test(location.href); + +function setDebug(value, filter) { + debug = value; + libraryFilter = filter; +} + +var libraryFilter = function () { + return true; +}; + +var NEEDS_THROW_FOR_STACK = !new Error("").stack; + +function getErrorWithStack() { + "use strict"; + + if (NEEDS_THROW_FOR_STACK) try { + // Doing something naughty in strict mode here to trigger a specific error + // that can be explicitely ignored in debugger's exception settings. + // If we'd just throw new Error() here, IE's debugger's exception settings + // will just consider it as "exception thrown by javascript code" which is + // something you wouldn't want it to ignore. + getErrorWithStack.arguments; + throw new Error(); // Fallback if above line don't throw. + } catch (e) { + return e; + } + return new Error(); +} + +function prettyStack(exception, numIgnoredFrames) { + var stack = exception.stack; + if (!stack) return ""; + numIgnoredFrames = numIgnoredFrames || 0; + if (stack.indexOf(exception.name) === 0) numIgnoredFrames += (exception.name + exception.message).split('\n').length; + return stack.split('\n').slice(numIgnoredFrames).filter(libraryFilter).map(function (frame) { + return "\n" + frame; + }).join(''); +} + +function deprecated(what, fn) { + return function () { + console.warn(what + " is deprecated. See https://github.com/dfahlander/Dexie.js/wiki/Deprecations. " + prettyStack(getErrorWithStack(), 1)); + return fn.apply(this, arguments); + }; +} + +var dexieErrorNames = ['Modify', 'Bulk', 'OpenFailed', 'VersionChange', 'Schema', 'Upgrade', 'InvalidTable', 'MissingAPI', 'NoSuchDatabase', 'InvalidArgument', 'SubTransaction', 'Unsupported', 'Internal', 'DatabaseClosed', 'PrematureCommit', 'ForeignAwait']; + +var idbDomErrorNames = ['Unknown', 'Constraint', 'Data', 'TransactionInactive', 'ReadOnly', 'Version', 'NotFound', 'InvalidState', 'InvalidAccess', 'Abort', 'Timeout', 'QuotaExceeded', 'Syntax', 'DataClone']; + +var errorList = dexieErrorNames.concat(idbDomErrorNames); + +var defaultTexts = { + VersionChanged: "Database version changed by other database connection", + DatabaseClosed: "Database has been closed", + Abort: "Transaction aborted", + TransactionInactive: "Transaction has already completed or failed" +}; + +// +// DexieError - base class of all out exceptions. +// +function DexieError(name, msg) { + // Reason we don't use ES6 classes is because: + // 1. It bloats transpiled code and increases size of minified code. + // 2. It doesn't give us much in this case. + // 3. It would require sub classes to call super(), which + // is not needed when deriving from Error. + this._e = getErrorWithStack(); + this.name = name; + this.message = msg; +} + +derive(DexieError).from(Error).extend({ + stack: { + get: function () { + return this._stack || (this._stack = this.name + ": " + this.message + prettyStack(this._e, 2)); + } + }, + toString: function () { + return this.name + ": " + this.message; + } +}); + +function getMultiErrorMessage(msg, failures) { + return msg + ". Errors: " + failures.map(function (f) { + return f.toString(); + }).filter(function (v, i, s) { + return s.indexOf(v) === i; + }) // Only unique error strings + .join('\n'); +} + +// +// ModifyError - thrown in Collection.modify() +// Specific constructor because it contains members failures and failedKeys. +// +function ModifyError(msg, failures, successCount, failedKeys) { + this._e = getErrorWithStack(); + this.failures = failures; + this.failedKeys = failedKeys; + this.successCount = successCount; +} +derive(ModifyError).from(DexieError); + +function BulkError(msg, failures) { + this._e = getErrorWithStack(); + this.name = "BulkError"; + this.failures = failures; + this.message = getMultiErrorMessage(msg, failures); +} +derive(BulkError).from(DexieError); + +// +// +// Dynamically generate error names and exception classes based +// on the names in errorList. +// +// + +// Map of {ErrorName -> ErrorName + "Error"} +var errnames = errorList.reduce(function (obj, name) { + return obj[name] = name + "Error", obj; +}, {}); + +// Need an alias for DexieError because we're gonna create subclasses with the same name. +var BaseException = DexieError; +// Map of {ErrorName -> exception constructor} +var exceptions = errorList.reduce(function (obj, name) { + // Let the name be "DexieError" because this name may + // be shown in call stack and when debugging. DexieError is + // the most true name because it derives from DexieError, + // and we cannot change Function.name programatically without + // dynamically create a Function object, which would be considered + // 'eval-evil'. + var fullName = name + "Error"; + function DexieError(msgOrInner, inner) { + this._e = getErrorWithStack(); + this.name = fullName; + if (!msgOrInner) { + this.message = defaultTexts[name] || fullName; + this.inner = null; + } else if (typeof msgOrInner === 'string') { + this.message = msgOrInner; + this.inner = inner || null; + } else if (typeof msgOrInner === 'object') { + this.message = msgOrInner.name + ' ' + msgOrInner.message; + this.inner = msgOrInner; + } + } + derive(DexieError).from(BaseException); + obj[name] = DexieError; + return obj; +}, {}); + +// Use ECMASCRIPT standard exceptions where applicable: +exceptions.Syntax = SyntaxError; +exceptions.Type = TypeError; +exceptions.Range = RangeError; + +var exceptionMap = idbDomErrorNames.reduce(function (obj, name) { + obj[name + "Error"] = exceptions[name]; + return obj; +}, {}); + +function mapError(domError, message) { + if (!domError || domError instanceof DexieError || domError instanceof TypeError || domError instanceof SyntaxError || !domError.name || !exceptionMap[domError.name]) return domError; + var rv = new exceptionMap[domError.name](message || domError.message, domError); + if ("stack" in domError) { + // Derive stack from inner exception if it has a stack + setProp(rv, "stack", { get: function () { + return this.inner.stack; + } }); + } + return rv; +} + +var fullNameExceptions = errorList.reduce(function (obj, name) { + if (["Syntax", "Type", "Range"].indexOf(name) === -1) obj[name + "Error"] = exceptions[name]; + return obj; +}, {}); + +fullNameExceptions.ModifyError = ModifyError; +fullNameExceptions.DexieError = DexieError; +fullNameExceptions.BulkError = BulkError; + +function nop() {} +function mirror(val) { + return val; +} +function pureFunctionChain(f1, f2) { + // Enables chained events that takes ONE argument and returns it to the next function in chain. + // This pattern is used in the hook("reading") event. + if (f1 == null || f1 === mirror) return f2; + return function (val) { + return f2(f1(val)); + }; +} + +function callBoth(on1, on2) { + return function () { + on1.apply(this, arguments); + on2.apply(this, arguments); + }; +} + +function hookCreatingChain(f1, f2) { + // Enables chained events that takes several arguments and may modify first argument by making a modification and then returning the same instance. + // This pattern is used in the hook("creating") event. + if (f1 === nop) return f2; + return function () { + var res = f1.apply(this, arguments); + if (res !== undefined) arguments[0] = res; + var onsuccess = this.onsuccess, + // In case event listener has set this.onsuccess + onerror = this.onerror; // In case event listener has set this.onerror + this.onsuccess = null; + this.onerror = null; + var res2 = f2.apply(this, arguments); + if (onsuccess) this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; + if (onerror) this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; + return res2 !== undefined ? res2 : res; + }; +} + +function hookDeletingChain(f1, f2) { + if (f1 === nop) return f2; + return function () { + f1.apply(this, arguments); + var onsuccess = this.onsuccess, + // In case event listener has set this.onsuccess + onerror = this.onerror; // In case event listener has set this.onerror + this.onsuccess = this.onerror = null; + f2.apply(this, arguments); + if (onsuccess) this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; + if (onerror) this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; + }; +} + +function hookUpdatingChain(f1, f2) { + if (f1 === nop) return f2; + return function (modifications) { + var res = f1.apply(this, arguments); + extend(modifications, res); // If f1 returns new modifications, extend caller's modifications with the result before calling next in chain. + var onsuccess = this.onsuccess, + // In case event listener has set this.onsuccess + onerror = this.onerror; // In case event listener has set this.onerror + this.onsuccess = null; + this.onerror = null; + var res2 = f2.apply(this, arguments); + if (onsuccess) this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; + if (onerror) this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; + return res === undefined ? res2 === undefined ? undefined : res2 : extend(res, res2); + }; +} + +function reverseStoppableEventChain(f1, f2) { + if (f1 === nop) return f2; + return function () { + if (f2.apply(this, arguments) === false) return false; + return f1.apply(this, arguments); + }; +} + + + +function promisableChain(f1, f2) { + if (f1 === nop) return f2; + return function () { + var res = f1.apply(this, arguments); + if (res && typeof res.then === 'function') { + var thiz = this, + i = arguments.length, + args = new Array(i); + while (i--) { + args[i] = arguments[i]; + }return res.then(function () { + return f2.apply(thiz, args); + }); + } + return f2.apply(this, arguments); + }; +} + +// +// Promise and Zone (PSD) for Dexie library +// +// I started out writing this Promise class by copying promise-light (https://github.com/taylorhakes/promise-light) by +// https://github.com/taylorhakes - an A+ and ECMASCRIPT 6 compliant Promise implementation. +// +// In previous versions this was fixed by not calling setTimeout when knowing that the resolve() or reject() came from another +// tick. In Dexie v1.4.0, I've rewritten the Promise class entirely. Just some fragments of promise-light is left. I use +// another strategy now that simplifies everything a lot: to always execute callbacks in a new micro-task, but have an own micro-task +// engine that is indexedDB compliant across all browsers. +// Promise class has also been optimized a lot with inspiration from bluebird - to avoid closures as much as possible. +// Also with inspiration from bluebird, asyncronic stacks in debug mode. +// +// Specific non-standard features of this Promise class: +// * Custom zone support (a.k.a. PSD) with ability to keep zones also when using native promises as well as +// native async / await. +// * Promise.follow() method built upon the custom zone engine, that allows user to track all promises created from current stack frame +// and below + all promises that those promises creates or awaits. +// * Detect any unhandled promise in a PSD-scope (PSD.onunhandled). +// +// David Fahlander, https://github.com/dfahlander +// + +// Just a pointer that only this module knows about. +// Used in Promise constructor to emulate a private constructor. +var INTERNAL = {}; + +// Async stacks (long stacks) must not grow infinitely. +var LONG_STACKS_CLIP_LIMIT = 100; +var MAX_LONG_STACKS = 20; +var ZONE_ECHO_LIMIT = 7; +var nativePromiseInstanceAndProto = function () { + try { + // Be able to patch native async functions + return new Function('let F=async ()=>{},p=F();return [p,Object.getPrototypeOf(p),Promise.resolve(),F.constructor];')(); + } catch (e) { + var P = _global.Promise; + return P ? [P.resolve(), P.prototype, P.resolve()] : []; + } +}(); +var resolvedNativePromise = nativePromiseInstanceAndProto[0]; +var nativePromiseProto = nativePromiseInstanceAndProto[1]; +var resolvedGlobalPromise = nativePromiseInstanceAndProto[2]; +var nativePromiseThen = nativePromiseProto && nativePromiseProto.then; + +var NativePromise = resolvedNativePromise && resolvedNativePromise.constructor; +var AsyncFunction = nativePromiseInstanceAndProto[3]; + +var stack_being_generated = false; + +/* The default function used only for the very first promise in a promise chain. + As soon as then promise is resolved or rejected, all next tasks will be executed in micro ticks + emulated in this module. For indexedDB compatibility, this means that every method needs to + execute at least one promise before doing an indexedDB operation. Dexie will always call + db.ready().then() for every operation to make sure the indexedDB event is started in an + indexedDB-compatible emulated micro task loop. +*/ +var schedulePhysicalTick = resolvedGlobalPromise ? function () { + resolvedGlobalPromise.then(physicalTick); +} : _global.setImmediate ? +// setImmediate supported. Those modern platforms also supports Function.bind(). +setImmediate.bind(null, physicalTick) : _global.MutationObserver ? +// MutationObserver supported +function () { + var hiddenDiv = document.createElement("div"); + new MutationObserver(function () { + physicalTick(); + hiddenDiv = null; + }).observe(hiddenDiv, { attributes: true }); + hiddenDiv.setAttribute('i', '1'); +} : +// No support for setImmediate or MutationObserver. No worry, setTimeout is only called +// once time. Every tick that follows will be our emulated micro tick. +// Could have uses setTimeout.bind(null, 0, physicalTick) if it wasnt for that FF13 and below has a bug +function () { + setTimeout(physicalTick, 0); +}; + +// Configurable through Promise.scheduler. +// Don't export because it would be unsafe to let unknown +// code call it unless they do try..catch within their callback. +// This function can be retrieved through getter of Promise.scheduler though, +// but users must not do Promise.scheduler = myFuncThatThrowsException +var asap$1 = function (callback, args) { + microtickQueue.push([callback, args]); + if (needsNewPhysicalTick) { + schedulePhysicalTick(); + needsNewPhysicalTick = false; + } +}; + +var isOutsideMicroTick = true; +var needsNewPhysicalTick = true; +var unhandledErrors = []; +var rejectingErrors = []; +var currentFulfiller = null; +var rejectionMapper = mirror; // Remove in next major when removing error mapping of DOMErrors and DOMExceptions + +var globalPSD = { + id: 'global', + global: true, + ref: 0, + unhandleds: [], + onunhandled: globalError, + pgp: false, + env: {}, + finalize: function () { + this.unhandleds.forEach(function (uh) { + try { + globalError(uh[0], uh[1]); + } catch (e) {} + }); + } +}; + +var PSD = globalPSD; + +var microtickQueue = []; // Callbacks to call in this or next physical tick. +var numScheduledCalls = 0; // Number of listener-calls left to do in this physical tick. +var tickFinalizers = []; // Finalizers to call when there are no more async calls scheduled within current physical tick. + +function Promise(fn) { + if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); + this._listeners = []; + this.onuncatched = nop; // Deprecate in next major. Not needed. Better to use global error handler. + + // A library may set `promise._lib = true;` after promise is created to make resolve() or reject() + // execute the microtask engine implicitely within the call to resolve() or reject(). + // To remain A+ compliant, a library must only set `_lib=true` if it can guarantee that the stack + // only contains library code when calling resolve() or reject(). + // RULE OF THUMB: ONLY set _lib = true for promises explicitely resolving/rejecting directly from + // global scope (event handler, timer etc)! + this._lib = false; + // Current async scope + var psd = this._PSD = PSD; + + if (debug) { + this._stackHolder = getErrorWithStack(); + this._prev = null; + this._numPrev = 0; // Number of previous promises (for long stacks) + } + + if (typeof fn !== 'function') { + if (fn !== INTERNAL) throw new TypeError('Not a function'); + // Private constructor (INTERNAL, state, value). + // Used internally by Promise.resolve() and Promise.reject(). + this._state = arguments[1]; + this._value = arguments[2]; + if (this._state === false) handleRejection(this, this._value); // Map error, set stack and addPossiblyUnhandledError(). + return; + } + + this._state = null; // null (=pending), false (=rejected) or true (=resolved) + this._value = null; // error or result + ++psd.ref; // Refcounting current scope + executePromiseTask(this, fn); +} + +// Prepare a property descriptor to put onto Promise.prototype.then +var thenProp = { + get: function () { + var psd = PSD, + microTaskId = totalEchoes; + + function then(onFulfilled, onRejected) { + var _this = this; + + var possibleAwait = !psd.global && (psd !== PSD || microTaskId !== totalEchoes); + if (possibleAwait) decrementExpectedAwaits(); + var rv = new Promise(function (resolve, reject) { + propagateToListener(_this, new Listener(nativeAwaitCompatibleWrap(onFulfilled, psd, possibleAwait), nativeAwaitCompatibleWrap(onRejected, psd, possibleAwait), resolve, reject, psd)); + }); + debug && linkToPreviousPromise(rv, this); + return rv; + } + + then._tQzo = true; // For idempotense, see setter below. + + return then; + }, + // Be idempotent and allow another framework (such as zone.js or another instance of a Dexie.Promise module) to replace Promise.prototype.then + // and when that framework wants to restore the original property, we must identify that and restore the original property descriptor. + set: function (value) { + setProp(this, 'then', value && value._tQzo ? thenProp : // Restore to original property descriptor. + { + get: function () { + return value; // Getter returning provided value (behaves like value is just changed) + }, + set: thenProp.set // Keep a setter that is prepared to restore original. + }); + } +}; + +props(Promise.prototype, { + then: thenProp, // Defined above. + _then: function (onFulfilled, onRejected) { + // A little tinier version of then() that don't have to create a resulting promise. + propagateToListener(this, new Listener(null, null, onFulfilled, onRejected, PSD)); + }, + + catch: function (onRejected) { + if (arguments.length === 1) return this.then(null, onRejected); + // First argument is the Error type to catch + var type = arguments[0], + handler = arguments[1]; + return typeof type === 'function' ? this.then(null, function (err) { + return ( + // Catching errors by its constructor type (similar to java / c++ / c#) + // Sample: promise.catch(TypeError, function (e) { ... }); + err instanceof type ? handler(err) : PromiseReject(err) + ); + }) : this.then(null, function (err) { + return ( + // Catching errors by the error.name property. Makes sense for indexedDB where error type + // is always DOMError but where e.name tells the actual error type. + // Sample: promise.catch('ConstraintError', function (e) { ... }); + err && err.name === type ? handler(err) : PromiseReject(err) + ); + }); + }, + + finally: function (onFinally) { + return this.then(function (value) { + onFinally(); + return value; + }, function (err) { + onFinally(); + return PromiseReject(err); + }); + }, + + stack: { + get: function () { + if (this._stack) return this._stack; + try { + stack_being_generated = true; + var stacks = getStack(this, [], MAX_LONG_STACKS); + var stack = stacks.join("\nFrom previous: "); + if (this._state !== null) this._stack = stack; // Stack may be updated on reject. + return stack; + } finally { + stack_being_generated = false; + } + } + }, + + timeout: function (ms, msg) { + var _this2 = this; + + return ms < Infinity ? new Promise(function (resolve, reject) { + var handle = setTimeout(function () { + return reject(new exceptions.Timeout(msg)); + }, ms); + _this2.then(resolve, reject).finally(clearTimeout.bind(null, handle)); + }) : this; + } +}); + +// Now that Promise.prototype is defined, we have all it takes to set globalPSD.env. +// Environment globals snapshotted on leaving global zone +globalPSD.env = snapShot(); + +function Listener(onFulfilled, onRejected, resolve, reject, zone) { + this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; + this.onRejected = typeof onRejected === 'function' ? onRejected : null; + this.resolve = resolve; + this.reject = reject; + this.psd = zone; +} + +// Promise Static Properties +props(Promise, { + all: function () { + var values = getArrayOf.apply(null, arguments) // Supports iterables, implicit arguments and array-like. + .map(onPossibleParallellAsync); // Handle parallell async/awaits + return new Promise(function (resolve, reject) { + if (values.length === 0) resolve([]); + var remaining = values.length; + values.forEach(function (a, i) { + return Promise.resolve(a).then(function (x) { + values[i] = x; + if (! --remaining) resolve(values); + }, reject); + }); + }); + }, + + resolve: function (value) { + if (value instanceof Promise) return value; + if (value && typeof value.then === 'function') return new Promise(function (resolve, reject) { + value.then(resolve, reject); + }); + var rv = new Promise(INTERNAL, true, value); + linkToPreviousPromise(rv, currentFulfiller); + return rv; + }, + + reject: PromiseReject, + + race: function () { + var values = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync); + return new Promise(function (resolve, reject) { + values.map(function (value) { + return Promise.resolve(value).then(resolve, reject); + }); + }); + }, + + PSD: { + get: function () { + return PSD; + }, + set: function (value) { + return PSD = value; + } + }, + + //totalEchoes: {get: ()=>totalEchoes}, + + //task: {get: ()=>task}, + + newPSD: newScope, + + usePSD: usePSD, + + scheduler: { + get: function () { + return asap$1; + }, + set: function (value) { + asap$1 = value; + } + }, + + rejectionMapper: { + get: function () { + return rejectionMapper; + }, + set: function (value) { + rejectionMapper = value; + } // Map reject failures + }, + + follow: function (fn, zoneProps) { + return new Promise(function (resolve, reject) { + return newScope(function (resolve, reject) { + var psd = PSD; + psd.unhandleds = []; // For unhandled standard- or 3rd party Promises. Checked at psd.finalize() + psd.onunhandled = reject; // Triggered directly on unhandled promises of this library. + psd.finalize = callBoth(function () { + var _this3 = this; + + // Unhandled standard or 3rd part promises are put in PSD.unhandleds and + // examined upon scope completion while unhandled rejections in this Promise + // will trigger directly through psd.onunhandled + run_at_end_of_this_or_next_physical_tick(function () { + _this3.unhandleds.length === 0 ? resolve() : reject(_this3.unhandleds[0]); + }); + }, psd.finalize); + fn(); + }, zoneProps, resolve, reject); + }); + } +}); + +/** +* Take a potentially misbehaving resolver function and make sure +* onFulfilled and onRejected are only called once. +* +* Makes no guarantees about asynchrony. +*/ +function executePromiseTask(promise, fn) { + // Promise Resolution Procedure: + // https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure + try { + fn(function (value) { + if (promise._state !== null) return; // Already settled + if (value === promise) throw new TypeError('A promise cannot be resolved with itself.'); + var shouldExecuteTick = promise._lib && beginMicroTickScope(); + if (value && typeof value.then === 'function') { + executePromiseTask(promise, function (resolve, reject) { + value instanceof Promise ? value._then(resolve, reject) : value.then(resolve, reject); + }); + } else { + promise._state = true; + promise._value = value; + propagateAllListeners(promise); + } + if (shouldExecuteTick) endMicroTickScope(); + }, handleRejection.bind(null, promise)); // If Function.bind is not supported. Exception is handled in catch below + } catch (ex) { + handleRejection(promise, ex); + } +} + +function handleRejection(promise, reason) { + rejectingErrors.push(reason); + if (promise._state !== null) return; + var shouldExecuteTick = promise._lib && beginMicroTickScope(); + reason = rejectionMapper(reason); + promise._state = false; + promise._value = reason; + debug && reason !== null && typeof reason === 'object' && !reason._promise && tryCatch(function () { + var origProp = getPropertyDescriptor(reason, "stack"); + reason._promise = promise; + setProp(reason, "stack", { + get: function () { + return stack_being_generated ? origProp && (origProp.get ? origProp.get.apply(reason) : origProp.value) : promise.stack; + } + }); + }); + // Add the failure to a list of possibly uncaught errors + addPossiblyUnhandledError(promise); + propagateAllListeners(promise); + if (shouldExecuteTick) endMicroTickScope(); +} + +function propagateAllListeners(promise) { + //debug && linkToPreviousPromise(promise); + var listeners = promise._listeners; + promise._listeners = []; + for (var i = 0, len = listeners.length; i < len; ++i) { + propagateToListener(promise, listeners[i]); + } + var psd = promise._PSD; + --psd.ref || psd.finalize(); // if psd.ref reaches zero, call psd.finalize(); + if (numScheduledCalls === 0) { + // If numScheduledCalls is 0, it means that our stack is not in a callback of a scheduled call, + // and that no deferreds where listening to this rejection or success. + // Since there is a risk that our stack can contain application code that may + // do stuff after this code is finished that may generate new calls, we cannot + // call finalizers here. + ++numScheduledCalls; + asap$1(function () { + if (--numScheduledCalls === 0) finalizePhysicalTick(); // Will detect unhandled errors + }, []); + } +} + +function propagateToListener(promise, listener) { + if (promise._state === null) { + promise._listeners.push(listener); + return; + } + + var cb = promise._state ? listener.onFulfilled : listener.onRejected; + if (cb === null) { + // This Listener doesnt have a listener for the event being triggered (onFulfilled or onReject) so lets forward the event to any eventual listeners on the Promise instance returned by then() or catch() + return (promise._state ? listener.resolve : listener.reject)(promise._value); + } + ++listener.psd.ref; + ++numScheduledCalls; + asap$1(callListener, [cb, promise, listener]); +} + +function callListener(cb, promise, listener) { + try { + // Set static variable currentFulfiller to the promise that is being fullfilled, + // so that we connect the chain of promises (for long stacks support) + currentFulfiller = promise; + + // Call callback and resolve our listener with it's return value. + var ret, + value = promise._value; + + if (promise._state) { + // cb is onResolved + ret = cb(value); + } else { + // cb is onRejected + if (rejectingErrors.length) rejectingErrors = []; + ret = cb(value); + if (rejectingErrors.indexOf(value) === -1) markErrorAsHandled(promise); // Callback didnt do Promise.reject(err) nor reject(err) onto another promise. + } + listener.resolve(ret); + } catch (e) { + // Exception thrown in callback. Reject our listener. + listener.reject(e); + } finally { + // Restore env and currentFulfiller. + currentFulfiller = null; + if (--numScheduledCalls === 0) finalizePhysicalTick(); + --listener.psd.ref || listener.psd.finalize(); + } +} + +function getStack(promise, stacks, limit) { + if (stacks.length === limit) return stacks; + var stack = ""; + if (promise._state === false) { + var failure = promise._value, + errorName, + message; + + if (failure != null) { + errorName = failure.name || "Error"; + message = failure.message || failure; + stack = prettyStack(failure, 0); + } else { + errorName = failure; // If error is undefined or null, show that. + message = ""; + } + stacks.push(errorName + (message ? ": " + message : "") + stack); + } + if (debug) { + stack = prettyStack(promise._stackHolder, 2); + if (stack && stacks.indexOf(stack) === -1) stacks.push(stack); + if (promise._prev) getStack(promise._prev, stacks, limit); + } + return stacks; +} + +function linkToPreviousPromise(promise, prev) { + // Support long stacks by linking to previous completed promise. + var numPrev = prev ? prev._numPrev + 1 : 0; + if (numPrev < LONG_STACKS_CLIP_LIMIT) { + // Prohibit infinite Promise loops to get an infinite long memory consuming "tail". + promise._prev = prev; + promise._numPrev = numPrev; + } +} + +/* The callback to schedule with setImmediate() or setTimeout(). + It runs a virtual microtick and executes any callback registered in microtickQueue. + */ +function physicalTick() { + beginMicroTickScope() && endMicroTickScope(); +} + +function beginMicroTickScope() { + var wasRootExec = isOutsideMicroTick; + isOutsideMicroTick = false; + needsNewPhysicalTick = false; + return wasRootExec; +} + +/* Executes micro-ticks without doing try..catch. + This can be possible because we only use this internally and + the registered functions are exception-safe (they do try..catch + internally before calling any external method). If registering + functions in the microtickQueue that are not exception-safe, this + would destroy the framework and make it instable. So we don't export + our asap method. +*/ +function endMicroTickScope() { + var callbacks, i, l; + do { + while (microtickQueue.length > 0) { + callbacks = microtickQueue; + microtickQueue = []; + l = callbacks.length; + for (i = 0; i < l; ++i) { + var item = callbacks[i]; + item[0].apply(null, item[1]); + } + } + } while (microtickQueue.length > 0); + isOutsideMicroTick = true; + needsNewPhysicalTick = true; +} + +function finalizePhysicalTick() { + var unhandledErrs = unhandledErrors; + unhandledErrors = []; + unhandledErrs.forEach(function (p) { + p._PSD.onunhandled.call(null, p._value, p); + }); + var finalizers = tickFinalizers.slice(0); // Clone first because finalizer may remove itself from list. + var i = finalizers.length; + while (i) { + finalizers[--i](); + } +} + +function run_at_end_of_this_or_next_physical_tick(fn) { + function finalizer() { + fn(); + tickFinalizers.splice(tickFinalizers.indexOf(finalizer), 1); + } + tickFinalizers.push(finalizer); + ++numScheduledCalls; + asap$1(function () { + if (--numScheduledCalls === 0) finalizePhysicalTick(); + }, []); +} + +function addPossiblyUnhandledError(promise) { + // Only add to unhandledErrors if not already there. The first one to add to this list + // will be upon the first rejection so that the root cause (first promise in the + // rejection chain) is the one listed. + if (!unhandledErrors.some(function (p) { + return p._value === promise._value; + })) unhandledErrors.push(promise); +} + +function markErrorAsHandled(promise) { + // Called when a reject handled is actually being called. + // Search in unhandledErrors for any promise whos _value is this promise_value (list + // contains only rejected promises, and only one item per error) + var i = unhandledErrors.length; + while (i) { + if (unhandledErrors[--i]._value === promise._value) { + // Found a promise that failed with this same error object pointer, + // Remove that since there is a listener that actually takes care of it. + unhandledErrors.splice(i, 1); + return; + } + } +} + +function PromiseReject(reason) { + return new Promise(INTERNAL, false, reason); +} + +function wrap(fn, errorCatcher) { + var psd = PSD; + return function () { + var wasRootExec = beginMicroTickScope(), + outerScope = PSD; + + try { + switchToZone(psd, true); + return fn.apply(this, arguments); + } catch (e) { + errorCatcher && errorCatcher(e); + } finally { + switchToZone(outerScope, false); + if (wasRootExec) endMicroTickScope(); + } + }; +} + +// +// variables used for native await support +// +var task = { awaits: 0, echoes: 0, id: 0 }; // The ongoing macro-task when using zone-echoing. +var taskCounter = 0; // ID counter for macro tasks. +var zoneStack = []; // Stack of left zones to restore asynchronically. +var zoneEchoes = 0; // zoneEchoes is a must in order to persist zones between native await expressions. +var totalEchoes = 0; // ID counter for micro-tasks. Used to detect possible native await in our Promise.prototype.then. + + +var zone_id_counter = 0; +function newScope(fn, props$$1, a1, a2) { + var parent = PSD, + psd = Object.create(parent); + psd.parent = parent; + psd.ref = 0; + psd.global = false; + psd.id = ++zone_id_counter; + // Prepare for promise patching (done in usePSD): + var globalEnv = globalPSD.env; + psd.env = nativePromiseThen ? { + Promise: Promise, // Changing window.Promise could be omitted for Chrome and Edge, where IDB+Promise plays well! + all: Promise.all, + race: Promise.race, + resolve: Promise.resolve, + reject: Promise.reject, + nthen: getPatchedPromiseThen(globalEnv.nthen, psd), // native then + gthen: getPatchedPromiseThen(globalEnv.gthen, psd) // global then + } : {}; + if (props$$1) extend(psd, props$$1); + + // unhandleds and onunhandled should not be specifically set here. + // Leave them on parent prototype. + // unhandleds.push(err) will push to parent's prototype + // onunhandled() will call parents onunhandled (with this scope's this-pointer though!) + ++parent.ref; + psd.finalize = function () { + --this.parent.ref || this.parent.finalize(); + }; + var rv = usePSD(psd, fn, a1, a2); + if (psd.ref === 0) psd.finalize(); + return rv; +} + +// Function to call if scopeFunc returns NativePromise +// Also for each NativePromise in the arguments to Promise.all() +function incrementExpectedAwaits() { + if (!task.id) task.id = ++taskCounter; + ++task.awaits; + task.echoes += ZONE_ECHO_LIMIT; + return task.id; +} +// Function to call when 'then' calls back on a native promise where onAwaitExpected() had been called. +// Also call this when a native await calls then method on a promise. In that case, don't supply +// sourceTaskId because we already know it refers to current task. +function decrementExpectedAwaits(sourceTaskId) { + if (!task.awaits || sourceTaskId && sourceTaskId !== task.id) return; + if (--task.awaits === 0) task.id = 0; + task.echoes = task.awaits * ZONE_ECHO_LIMIT; // Will reset echoes to 0 if awaits is 0. +} + +// Call from Promise.all() and Promise.race() +function onPossibleParallellAsync(possiblePromise) { + if (task.echoes && possiblePromise && possiblePromise.constructor === NativePromise) { + incrementExpectedAwaits(); + return possiblePromise.then(function (x) { + decrementExpectedAwaits(); + return x; + }, function (e) { + decrementExpectedAwaits(); + return rejection(e); + }); + } + return possiblePromise; +} + +function zoneEnterEcho(targetZone) { + ++totalEchoes; + if (!task.echoes || --task.echoes === 0) { + task.echoes = task.id = 0; // Cancel zone echoing. + } + + zoneStack.push(PSD); + switchToZone(targetZone, true); +} + +function zoneLeaveEcho() { + var zone = zoneStack[zoneStack.length - 1]; + zoneStack.pop(); + switchToZone(zone, false); +} + +function switchToZone(targetZone, bEnteringZone) { + var currentZone = PSD; + if (bEnteringZone ? task.echoes && (!zoneEchoes++ || targetZone !== PSD) : zoneEchoes && (! --zoneEchoes || targetZone !== PSD)) { + // Enter or leave zone asynchronically as well, so that tasks initiated during current tick + // will be surrounded by the zone when they are invoked. + enqueueNativeMicroTask(bEnteringZone ? zoneEnterEcho.bind(null, targetZone) : zoneLeaveEcho); + } + if (targetZone === PSD) return; + + PSD = targetZone; // The actual zone switch occurs at this line. + + // Snapshot on every leave from global zone. + if (currentZone === globalPSD) globalPSD.env = snapShot(); + + var GlobalPromise = globalPSD.env.Promise; + if (GlobalPromise) { + // Global environment has a promise at this time. Polyfilled or not. Let's patch the global environment. + // Swich environments (may be PSD-zone or the global zone. Both apply.) + var targetEnv = targetZone.env; + + // Change Promise.prototype.then for native and global Promise (they MAY differ on polyfilled environments, but both can be accessed) + // Must be done on each zone change because the patched method contains targetZone in its closure. + nativePromiseProto.then = targetEnv.nthen; + GlobalPromise.prototype.then = targetEnv.gthen; + + if (currentZone.global || targetZone.global) { + // Leaving or entering global zone. It's time to patch / restore global Promise. + + // Set this Promise to window.Promise so that transiled async functions will work on Firefox, Safari and IE, as well as with Zonejs and angular. + _global.Promise = targetEnv.Promise; + + // Support Promise.all() etc to work indexedDB-safe also when people are including es6-promise as a module (they might + // not be accessing global.Promise but a local reference to it) + GlobalPromise.all = targetEnv.all; + GlobalPromise.race = targetEnv.race; + GlobalPromise.resolve = targetEnv.resolve; + GlobalPromise.reject = targetEnv.reject; + } + } +} + +function snapShot() { + var GlobalPromise = _global.Promise; + return GlobalPromise ? { + Promise: GlobalPromise, + all: GlobalPromise.all, + race: GlobalPromise.race, + resolve: GlobalPromise.resolve, + reject: GlobalPromise.reject, + nthen: nativePromiseProto.then, + gthen: GlobalPromise.prototype.then + } : {}; +} + +function usePSD(psd, fn, a1, a2, a3) { + var outerScope = PSD; + try { + switchToZone(psd, true); + return fn(a1, a2, a3); + } finally { + switchToZone(outerScope, false); + } +} + +function enqueueNativeMicroTask(job) { + // + // Precondition: nativePromiseThen !== undefined + // + nativePromiseThen.call(resolvedNativePromise, job); +} + +function nativeAwaitCompatibleWrap(fn, zone, possibleAwait) { + return typeof fn !== 'function' ? fn : function () { + var outerZone = PSD; + if (possibleAwait) incrementExpectedAwaits(); + switchToZone(zone, true); + try { + return fn.apply(this, arguments); + } finally { + switchToZone(outerZone, false); + } + }; +} + +function getPatchedPromiseThen(origThen, zone) { + return function (onResolved, onRejected) { + return origThen.call(this, nativeAwaitCompatibleWrap(onResolved, zone, false), nativeAwaitCompatibleWrap(onRejected, zone, false)); + }; +} + +var UNHANDLEDREJECTION = "unhandledrejection"; + +function globalError(err, promise) { + var rv; + try { + rv = promise.onuncatched(err); + } catch (e) {} + if (rv !== false) try { + var event, + eventData = { promise: promise, reason: err }; + if (_global.document && document.createEvent) { + event = document.createEvent('Event'); + event.initEvent(UNHANDLEDREJECTION, true, true); + extend(event, eventData); + } else if (_global.CustomEvent) { + event = new CustomEvent(UNHANDLEDREJECTION, { detail: eventData }); + extend(event, eventData); + } + if (event && _global.dispatchEvent) { + dispatchEvent(event); + if (!_global.PromiseRejectionEvent && _global.onunhandledrejection) + // No native support for PromiseRejectionEvent but user has set window.onunhandledrejection. Manually call it. + try { + _global.onunhandledrejection(event); + } catch (_) {} + } + if (!event.defaultPrevented) { + console.warn('Unhandled rejection: ' + (err.stack || err)); + } + } catch (e) {} +} + +doFakeAutoComplete(function () { + // Simplify the job for VS Intellisense. This piece of code is one of the keys to the new marvellous intellisense support in Dexie. + asap$1 = function (fn, args) { + setTimeout(function () { + fn.apply(null, args); + }, 0); + }; +}); + +var rejection = Promise.reject; + +function Events(ctx) { + var evs = {}; + var rv = function (eventName, subscriber) { + if (subscriber) { + // Subscribe. If additional arguments than just the subscriber was provided, forward them as well. + var i = arguments.length, + args = new Array(i - 1); + while (--i) { + args[i - 1] = arguments[i]; + }evs[eventName].subscribe.apply(null, args); + return ctx; + } else if (typeof eventName === 'string') { + // Return interface allowing to fire or unsubscribe from event + return evs[eventName]; + } + }; + rv.addEventType = add; + + for (var i = 1, l = arguments.length; i < l; ++i) { + add(arguments[i]); + } + + return rv; + + function add(eventName, chainFunction, defaultFunction) { + if (typeof eventName === 'object') return addConfiguredEvents(eventName); + if (!chainFunction) chainFunction = reverseStoppableEventChain; + if (!defaultFunction) defaultFunction = nop; + + var context = { + subscribers: [], + fire: defaultFunction, + subscribe: function (cb) { + if (context.subscribers.indexOf(cb) === -1) { + context.subscribers.push(cb); + context.fire = chainFunction(context.fire, cb); + } + }, + unsubscribe: function (cb) { + context.subscribers = context.subscribers.filter(function (fn) { + return fn !== cb; + }); + context.fire = context.subscribers.reduce(chainFunction, defaultFunction); + } + }; + evs[eventName] = rv[eventName] = context; + return context; + } + + function addConfiguredEvents(cfg) { + // events(this, {reading: [functionChain, nop]}); + keys(cfg).forEach(function (eventName) { + var args = cfg[eventName]; + if (isArray(args)) { + add(eventName, cfg[eventName][0], cfg[eventName][1]); + } else if (args === 'asap') { + // Rather than approaching event subscription using a functional approach, we here do it in a for-loop where subscriber is executed in its own stack + // enabling that any exception that occur wont disturb the initiator and also not nescessary be catched and forgotten. + var context = add(eventName, mirror, function fire() { + // Optimazation-safe cloning of arguments into args. + var i = arguments.length, + args = new Array(i); + while (i--) { + args[i] = arguments[i]; + } // All each subscriber: + context.subscribers.forEach(function (fn) { + asap(function fireEvent() { + fn.apply(null, args); + }); + }); + }); + } else throw new exceptions.InvalidArgument("Invalid event config"); + }); + } +} + +/* + * Dexie.js - a minimalistic wrapper for IndexedDB + * =============================================== + * + * By David Fahlander, david.fahlander@gmail.com + * + * Version 2.0.0-beta.6, Mon Nov 28 2016 + * + * http://dexie.org + * + * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/ + */ + +var DEXIE_VERSION = '2.0.0-beta.6'; +var maxString = String.fromCharCode(65535); +var maxKey = function () { + try { + IDBKeyRange.only([[]]);return [[]]; + } catch (e) { + return maxString; + } +}(); +var minKey = -Infinity; +var INVALID_KEY_ARGUMENT = "Invalid key provided. Keys must be of type string, number, Date or Array."; +var STRING_EXPECTED = "String expected."; +var connections = []; +var isIEOrEdge = typeof navigator !== 'undefined' && /(MSIE|Trident|Edge)/.test(navigator.userAgent); +var hasIEDeleteObjectStoreBug = isIEOrEdge; +var hangsOnDeleteLargeKeyRange = isIEOrEdge; +var dexieStackFrameFilter = function (frame) { + return !/(dexie\.js|dexie\.min\.js)/.test(frame); +}; + +setDebug(debug, dexieStackFrameFilter); + +function Dexie(dbName, options) { + /// Specify only if you wich to control which addons that should run on this instance + var deps = Dexie.dependencies; + var opts = extend({ + // Default Options + addons: Dexie.addons, // Pick statically registered addons by default + autoOpen: true, // Don't require db.open() explicitely. + indexedDB: deps.indexedDB, // Backend IndexedDB api. Default to IDBShim or browser env. + IDBKeyRange: deps.IDBKeyRange // Backend IDBKeyRange api. Default to IDBShim or browser env. + }, options); + var addons = opts.addons, + autoOpen = opts.autoOpen, + indexedDB = opts.indexedDB, + IDBKeyRange = opts.IDBKeyRange; + + var globalSchema = this._dbSchema = {}; + var versions = []; + var dbStoreNames = []; + var allTables = {}; + /// + var idbdb = null; // Instance of IDBDatabase + var dbOpenError = null; + var isBeingOpened = false; + var openComplete = false; + var READONLY = "readonly", + READWRITE = "readwrite"; + var db = this; + var dbReadyResolve, + dbReadyPromise = new Promise(function (resolve) { + dbReadyResolve = resolve; + }), + cancelOpen, + openCanceller = new Promise(function (_, reject) { + cancelOpen = reject; + }); + var autoSchema = true; + var hasNativeGetDatabaseNames = !!getNativeGetDatabaseNamesFn(indexedDB), + hasGetAll; + + function init() { + // Default subscribers to "versionchange" and "blocked". + // Can be overridden by custom handlers. If custom handlers return false, these default + // behaviours will be prevented. + db.on("versionchange", function (ev) { + // Default behavior for versionchange event is to close database connection. + // Caller can override this behavior by doing db.on("versionchange", function(){ return false; }); + // Let's not block the other window from making it's delete() or open() call. + // NOTE! This event is never fired in IE,Edge or Safari. + if (ev.newVersion > 0) console.warn('Another connection wants to upgrade database \'' + db.name + '\'. Closing db now to resume the upgrade.');else console.warn('Another connection wants to delete database \'' + db.name + '\'. Closing db now to resume the delete request.'); + db.close(); + // In many web applications, it would be recommended to force window.reload() + // when this event occurs. To do that, subscribe to the versionchange event + // and call window.location.reload(true) if ev.newVersion > 0 (not a deletion) + // The reason for this is that your current web app obviously has old schema code that needs + // to be updated. Another window got a newer version of the app and needs to upgrade DB but + // your window is blocking it unless we close it here. + }); + db.on("blocked", function (ev) { + if (!ev.newVersion || ev.newVersion < ev.oldVersion) console.warn('Dexie.delete(\'' + db.name + '\') was blocked');else console.warn('Upgrade \'' + db.name + '\' blocked by other connection holding version ' + ev.oldVersion / 10); + }); + } + + // + // + // + // ------------------------- Versioning Framework--------------------------- + // + // + // + + this.version = function (versionNumber) { + /// + /// + if (idbdb || isBeingOpened) throw new exceptions.Schema("Cannot add version when database is open"); + this.verno = Math.max(this.verno, versionNumber); + var versionInstance = versions.filter(function (v) { + return v._cfg.version === versionNumber; + })[0]; + if (versionInstance) return versionInstance; + versionInstance = new Version(versionNumber); + versions.push(versionInstance); + versions.sort(lowerVersionFirst); + // Disable autoschema mode, as at least one version is specified. + autoSchema = false; + return versionInstance; + }; + + function Version(versionNumber) { + this._cfg = { + version: versionNumber, + storesSource: null, + dbschema: {}, + tables: {}, + contentUpgrade: null + }; + this.stores({}); // Derive earlier schemas by default. + } + + extend(Version.prototype, { + stores: function (stores) { + /// + /// Defines the schema for a particular version + /// + /// + /// Example:
+ /// {users: "id++,first,last,&username,*email",
+ /// passwords: "id++,&username"}
+ ///
+ /// Syntax: {Table: "[primaryKey][++],[&][*]index1,[&][*]index2,..."}

+ /// Special characters:
+ /// "&" means unique key,
+ /// "*" means value is multiEntry,
+ /// "++" means auto-increment and only applicable for primary key
+ /// + this._cfg.storesSource = this._cfg.storesSource ? extend(this._cfg.storesSource, stores) : stores; + + // Derive stores from earlier versions if they are not explicitely specified as null or a new syntax. + var storesSpec = {}; + versions.forEach(function (version) { + // 'versions' is always sorted by lowest version first. + extend(storesSpec, version._cfg.storesSource); + }); + + var dbschema = this._cfg.dbschema = {}; + this._parseStoresSpec(storesSpec, dbschema); + // Update the latest schema to this version + // Update API + globalSchema = db._dbSchema = dbschema; + removeTablesApi([allTables, db, Transaction.prototype]); // Keep Transaction.prototype even though it should be depr. + setApiOnPlace([allTables, db, Transaction.prototype, this._cfg.tables], keys(dbschema), dbschema); + dbStoreNames = keys(dbschema); + return this; + }, + upgrade: function (upgradeFunction) { + /// Function that performs upgrading actions. + var self = this; + fakeAutoComplete(function () { + upgradeFunction(db._createTransaction(READWRITE, keys(self._cfg.dbschema), self._cfg.dbschema)); // BUGBUG: No code completion for prev version's tables wont appear. + }); + this._cfg.contentUpgrade = upgradeFunction; + return this; + }, + _parseStoresSpec: function (stores, outSchema) { + keys(stores).forEach(function (tableName) { + if (stores[tableName] !== null) { + var instanceTemplate = {}; + var indexes = parseIndexSyntax(stores[tableName]); + var primKey = indexes.shift(); + if (primKey.multi) throw new exceptions.Schema("Primary key cannot be multi-valued"); + if (primKey.keyPath) setByKeyPath(instanceTemplate, primKey.keyPath, primKey.auto ? 0 : primKey.keyPath); + indexes.forEach(function (idx) { + if (idx.auto) throw new exceptions.Schema("Only primary key can be marked as autoIncrement (++)"); + if (!idx.keyPath) throw new exceptions.Schema("Index must have a name and cannot be an empty string"); + setByKeyPath(instanceTemplate, idx.keyPath, idx.compound ? idx.keyPath.map(function () { + return ""; + }) : ""); + }); + outSchema[tableName] = new TableSchema(tableName, primKey, indexes, instanceTemplate); + } + }); + } + }); + + function runUpgraders(oldVersion, idbtrans, reject) { + var trans = db._createTransaction(READWRITE, dbStoreNames, globalSchema); + trans.create(idbtrans); + trans._completion.catch(reject); + var rejectTransaction = trans._reject.bind(trans); + newScope(function () { + PSD.trans = trans; + if (oldVersion === 0) { + // Create tables: + keys(globalSchema).forEach(function (tableName) { + createTable(idbtrans, tableName, globalSchema[tableName].primKey, globalSchema[tableName].indexes); + }); + Promise.follow(function () { + return db.on.populate.fire(trans); + }).catch(rejectTransaction); + } else updateTablesAndIndexes(oldVersion, trans, idbtrans).catch(rejectTransaction); + }); + } + + function updateTablesAndIndexes(oldVersion, trans, idbtrans) { + // Upgrade version to version, step-by-step from oldest to newest version. + // Each transaction object will contain the table set that was current in that version (but also not-yet-deleted tables from its previous version) + var queue = []; + var oldVersionStruct = versions.filter(function (version) { + return version._cfg.version === oldVersion; + })[0]; + if (!oldVersionStruct) throw new exceptions.Upgrade("Dexie specification of currently installed DB version is missing"); + globalSchema = db._dbSchema = oldVersionStruct._cfg.dbschema; + var anyContentUpgraderHasRun = false; + + var versToRun = versions.filter(function (v) { + return v._cfg.version > oldVersion; + }); + versToRun.forEach(function (version) { + /// + queue.push(function () { + var oldSchema = globalSchema; + var newSchema = version._cfg.dbschema; + adjustToExistingIndexNames(oldSchema, idbtrans); + adjustToExistingIndexNames(newSchema, idbtrans); + globalSchema = db._dbSchema = newSchema; + var diff = getSchemaDiff(oldSchema, newSchema); + // Add tables + diff.add.forEach(function (tuple) { + createTable(idbtrans, tuple[0], tuple[1].primKey, tuple[1].indexes); + }); + // Change tables + diff.change.forEach(function (change) { + if (change.recreate) { + throw new exceptions.Upgrade("Not yet support for changing primary key"); + } else { + var store = idbtrans.objectStore(change.name); + // Add indexes + change.add.forEach(function (idx) { + addIndex(store, idx); + }); + // Update indexes + change.change.forEach(function (idx) { + store.deleteIndex(idx.name); + addIndex(store, idx); + }); + // Delete indexes + change.del.forEach(function (idxName) { + store.deleteIndex(idxName); + }); + } + }); + if (version._cfg.contentUpgrade) { + anyContentUpgraderHasRun = true; + return Promise.follow(function () { + version._cfg.contentUpgrade(trans); + }); + } + }); + queue.push(function (idbtrans) { + if (!anyContentUpgraderHasRun || !hasIEDeleteObjectStoreBug) { + // Dont delete old tables if ieBug is present and a content upgrader has run. Let tables be left in DB so far. This needs to be taken care of. + var newSchema = version._cfg.dbschema; + // Delete old tables + deleteRemovedTables(newSchema, idbtrans); + } + }); + }); + + // Now, create a queue execution engine + function runQueue() { + return queue.length ? Promise.resolve(queue.shift()(trans.idbtrans)).then(runQueue) : Promise.resolve(); + } + + return runQueue().then(function () { + createMissingTables(globalSchema, idbtrans); // At last, make sure to create any missing tables. (Needed by addons that add stores to DB without specifying version) + }); + } + + function getSchemaDiff(oldSchema, newSchema) { + var diff = { + del: [], // Array of table names + add: [], // Array of [tableName, newDefinition] + change: [] // Array of {name: tableName, recreate: newDefinition, del: delIndexNames, add: newIndexDefs, change: changedIndexDefs} + }; + for (var table in oldSchema) { + if (!newSchema[table]) diff.del.push(table); + } + for (table in newSchema) { + var oldDef = oldSchema[table], + newDef = newSchema[table]; + if (!oldDef) { + diff.add.push([table, newDef]); + } else { + var change = { + name: table, + def: newDef, + recreate: false, + del: [], + add: [], + change: [] + }; + if (oldDef.primKey.src !== newDef.primKey.src) { + // Primary key has changed. Remove and re-add table. + change.recreate = true; + diff.change.push(change); + } else { + // Same primary key. Just find out what differs: + var oldIndexes = oldDef.idxByName; + var newIndexes = newDef.idxByName; + for (var idxName in oldIndexes) { + if (!newIndexes[idxName]) change.del.push(idxName); + } + for (idxName in newIndexes) { + var oldIdx = oldIndexes[idxName], + newIdx = newIndexes[idxName]; + if (!oldIdx) change.add.push(newIdx);else if (oldIdx.src !== newIdx.src) change.change.push(newIdx); + } + if (change.del.length > 0 || change.add.length > 0 || change.change.length > 0) { + diff.change.push(change); + } + } + } + } + return diff; + } + + function createTable(idbtrans, tableName, primKey, indexes) { + /// + var store = idbtrans.db.createObjectStore(tableName, primKey.keyPath ? { keyPath: primKey.keyPath, autoIncrement: primKey.auto } : { autoIncrement: primKey.auto }); + indexes.forEach(function (idx) { + addIndex(store, idx); + }); + return store; + } + + function createMissingTables(newSchema, idbtrans) { + keys(newSchema).forEach(function (tableName) { + if (!idbtrans.db.objectStoreNames.contains(tableName)) { + createTable(idbtrans, tableName, newSchema[tableName].primKey, newSchema[tableName].indexes); + } + }); + } + + function deleteRemovedTables(newSchema, idbtrans) { + for (var i = 0; i < idbtrans.db.objectStoreNames.length; ++i) { + var storeName = idbtrans.db.objectStoreNames[i]; + if (newSchema[storeName] == null) { + idbtrans.db.deleteObjectStore(storeName); + } + } + } + + function addIndex(store, idx) { + store.createIndex(idx.name, idx.keyPath, { unique: idx.unique, multiEntry: idx.multi }); + } + + // + // + // Dexie Protected API + // + // + + this._allTables = allTables; + + this._createTransaction = function (mode, storeNames, dbschema, parentTransaction) { + return new Transaction(mode, storeNames, dbschema, parentTransaction); + }; + + /* Generate a temporary transaction when db operations are done outside a transactino scope. + */ + function tempTransaction(mode, storeNames, fn) { + // Last argument is "writeLocked". But this doesnt apply to oneshot direct db operations, so we ignore it. + if (!openComplete && !PSD.letThrough) { + if (!isBeingOpened) { + if (!autoOpen) return rejection(new exceptions.DatabaseClosed()); + db.open().catch(nop); // Open in background. If if fails, it will be catched by the final promise anyway. + } + return dbReadyPromise.then(function () { + return tempTransaction(mode, storeNames, fn); + }); + } else { + var trans = db._createTransaction(mode, storeNames, globalSchema).create(); + return trans._promise(mode, function (resolve, reject) { + return newScope(function () { + // OPTIMIZATION POSSIBLE? newScope() not needed because it's already done in _promise. + PSD.trans = trans; + return fn(resolve, reject, trans); + }); + }).then(function (result) { + // Instead of resolving value directly, wait with resolving it until transaction has completed. + // Otherwise the data would not be in the DB if requesting it in the then() operation. + // Specifically, to ensure that the following expression will work: + // + // db.friends.put({name: "Arne"}).then(function () { + // db.friends.where("name").equals("Arne").count(function(count) { + // assert (count === 1); + // }); + // }); + // + return trans._completion.then(function () { + return result; + }); + }); /*.catch(err => { // Don't do this as of now. If would affect bulk- and modify methods in a way that could be more intuitive. But wait! Maybe change in next major. + trans._reject(err); + return rejection(err); + });*/ + } + } + + this._whenReady = function (fn) { + return fake || openComplete || PSD.letThrough ? fn() : new Promise(function (resolve, reject) { + if (!isBeingOpened) { + if (!autoOpen) { + reject(new exceptions.DatabaseClosed()); + return; + } + db.open().catch(nop); // Open in background. If if fails, it will be catched by the final promise anyway. + } + dbReadyPromise.then(resolve, reject); + }).then(fn); + }; + + // + // + // + // + // Dexie API + // + // + // + + this.verno = 0; + + this.open = function () { + if (isBeingOpened || idbdb) return dbReadyPromise.then(function () { + return dbOpenError ? rejection(dbOpenError) : db; + }); + debug && (openCanceller._stackHolder = getErrorWithStack()); // Let stacks point to when open() was called rather than where new Dexie() was called. + isBeingOpened = true; + dbOpenError = null; + openComplete = false; + + // Function pointers to call when the core opening process completes. + var resolveDbReady = dbReadyResolve, + + // upgradeTransaction to abort on failure. + upgradeTransaction = null; + + return Promise.race([openCanceller, new Promise(function (resolve, reject) { + doFakeAutoComplete(function () { + return resolve(); + }); + + // Multiply db.verno with 10 will be needed to workaround upgrading bug in IE: + // IE fails when deleting objectStore after reading from it. + // A future version of Dexie.js will stopover an intermediate version to workaround this. + // At that point, we want to be backward compatible. Could have been multiplied with 2, but by using 10, it is easier to map the number to the real version number. + + // If no API, throw! + if (!indexedDB) throw new exceptions.MissingAPI("indexedDB API not found. If using IE10+, make sure to run your code on a server URL " + "(not locally). If using old Safari versions, make sure to include indexedDB polyfill."); + + var req = autoSchema ? indexedDB.open(dbName) : indexedDB.open(dbName, Math.round(db.verno * 10)); + if (!req) throw new exceptions.MissingAPI("IndexedDB API not available"); // May happen in Safari private mode, see https://github.com/dfahlander/Dexie.js/issues/134 + req.onerror = eventRejectHandler(reject); + req.onblocked = wrap(fireOnBlocked); + req.onupgradeneeded = wrap(function (e) { + upgradeTransaction = req.transaction; + if (autoSchema && !db._allowEmptyDB) { + // Unless an addon has specified db._allowEmptyDB, lets make the call fail. + // Caller did not specify a version or schema. Doing that is only acceptable for opening alread existing databases. + // If onupgradeneeded is called it means database did not exist. Reject the open() promise and make sure that we + // do not create a new database by accident here. + req.onerror = preventDefault; // Prohibit onabort error from firing before we're done! + upgradeTransaction.abort(); // Abort transaction (would hope that this would make DB disappear but it doesnt.) + // Close database and delete it. + req.result.close(); + var delreq = indexedDB.deleteDatabase(dbName); // The upgrade transaction is atomic, and javascript is single threaded - meaning that there is no risk that we delete someone elses database here! + delreq.onsuccess = delreq.onerror = wrap(function () { + reject(new exceptions.NoSuchDatabase('Database ' + dbName + ' doesnt exist')); + }); + } else { + upgradeTransaction.onerror = eventRejectHandler(reject); + var oldVer = e.oldVersion > Math.pow(2, 62) ? 0 : e.oldVersion; // Safari 8 fix. + runUpgraders(oldVer / 10, upgradeTransaction, reject, req); + } + }, reject); + + req.onsuccess = wrap(function () { + // Core opening procedure complete. Now let's just record some stuff. + upgradeTransaction = null; + idbdb = req.result; + connections.push(db); // Used for emulating versionchange event on IE/Edge/Safari. + + if (autoSchema) readGlobalSchema();else if (idbdb.objectStoreNames.length > 0) { + try { + adjustToExistingIndexNames(globalSchema, idbdb.transaction(safariMultiStoreFix(idbdb.objectStoreNames), READONLY)); + } catch (e) { + // Safari may bail out if > 1 store names. However, this shouldnt be a showstopper. Issue #120. + } + } + + idbdb.onversionchange = wrap(function (ev) { + db._vcFired = true; // detect implementations that not support versionchange (IE/Edge/Safari) + db.on("versionchange").fire(ev); + }); + + if (!hasNativeGetDatabaseNames) { + // Update localStorage with list of database names + globalDatabaseList(function (databaseNames) { + if (databaseNames.indexOf(dbName) === -1) return databaseNames.push(dbName); + }); + } + + resolve(); + }, reject); + })]).then(function () { + // Before finally resolving the dbReadyPromise and this promise, + // call and await all on('ready') subscribers: + // Dexie.vip() makes subscribers able to use the database while being opened. + // This is a must since these subscribers take part of the opening procedure. + return Dexie.vip(db.on.ready.fire); + }).then(function () { + // Resolve the db.open() with the db instance. + isBeingOpened = false; + return db; + }).catch(function (err) { + try { + // Did we fail within onupgradeneeded? Make sure to abort the upgrade transaction so it doesnt commit. + upgradeTransaction && upgradeTransaction.abort(); + } catch (e) {} + isBeingOpened = false; // Set before calling db.close() so that it doesnt reject openCanceller again (leads to unhandled rejection event). + db.close(); // Closes and resets idbdb, removes connections, resets dbReadyPromise and openCanceller so that a later db.open() is fresh. + // A call to db.close() may have made on-ready subscribers fail. Use dbOpenError if set, since err could be a follow-up error on that. + dbOpenError = err; // Record the error. It will be used to reject further promises of db operations. + return rejection(dbOpenError); + }).finally(function () { + openComplete = true; + resolveDbReady(); // dbReadyPromise is resolved no matter if open() rejects or resolved. It's just to wake up waiters. + }); + }; + + this.close = function () { + var idx = connections.indexOf(db); + if (idx >= 0) connections.splice(idx, 1); + if (idbdb) { + try { + idbdb.close(); + } catch (e) {} + idbdb = null; + } + autoOpen = false; + dbOpenError = new exceptions.DatabaseClosed(); + if (isBeingOpened) cancelOpen(dbOpenError); + // Reset dbReadyPromise promise: + dbReadyPromise = new Promise(function (resolve) { + dbReadyResolve = resolve; + }); + openCanceller = new Promise(function (_, reject) { + cancelOpen = reject; + }); + }; + + this.delete = function () { + var hasArguments = arguments.length > 0; + return new Promise(function (resolve, reject) { + if (hasArguments) throw new exceptions.InvalidArgument("Arguments not allowed in db.delete()"); + if (isBeingOpened) { + dbReadyPromise.then(doDelete); + } else { + doDelete(); + } + function doDelete() { + db.close(); + var req = indexedDB.deleteDatabase(dbName); + req.onsuccess = wrap(function () { + if (!hasNativeGetDatabaseNames) { + globalDatabaseList(function (databaseNames) { + var pos = databaseNames.indexOf(dbName); + if (pos >= 0) return databaseNames.splice(pos, 1); + }); + } + resolve(); + }); + req.onerror = eventRejectHandler(reject); + req.onblocked = fireOnBlocked; + } + }); + }; + + this.backendDB = function () { + return idbdb; + }; + + this.isOpen = function () { + return idbdb !== null; + }; + this.hasFailed = function () { + return dbOpenError !== null; + }; + this.dynamicallyOpened = function () { + return autoSchema; + }; + + // + // Properties + // + this.name = dbName; + + // db.tables - an array of all Table instances. + setProp(this, "tables", { + get: function () { + /// + return keys(allTables).map(function (name) { + return allTables[name]; + }); + } + }); + + // + // Events + // + this.on = Events(this, "populate", "blocked", "versionchange", { ready: [promisableChain, nop] }); + + this.on.ready.subscribe = override(this.on.ready.subscribe, function (subscribe) { + return function (subscriber, bSticky) { + Dexie.vip(function () { + if (openComplete) { + // Database already open. Call subscriber asap. + if (!dbOpenError) Promise.resolve().then(subscriber); + // bSticky: Also subscribe to future open sucesses (after close / reopen) + if (bSticky) subscribe(subscriber); + } else { + // Database not yet open. Subscribe to it. + subscribe(subscriber); + // If bSticky is falsy, make sure to unsubscribe subscriber when fired once. + if (!bSticky) subscribe(function unsubscribe() { + db.on.ready.unsubscribe(subscriber); + db.on.ready.unsubscribe(unsubscribe); + }); + } + }); + }; + }); + + fakeAutoComplete(function () { + db.on("populate").fire(db._createTransaction(READWRITE, dbStoreNames, globalSchema)); + }); + + this.transaction = function () { + /// + /// + /// + /// "r" for readonly, or "rw" for readwrite + /// Table instance, Array of Table instances, String or String Array of object stores to include in the transaction + /// Function to execute with transaction + + var args = extractTransactionArgs.apply(this, arguments); + return this._transaction.apply(this, args); + }; + + function extractTransactionArgs(mode, _tableArgs_, scopeFunc) { + // Let table arguments be all arguments between mode and last argument. + var i = arguments.length; + if (i < 2) throw new exceptions.InvalidArgument("Too few arguments"); + // Prevent optimzation killer (https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments) + // and clone arguments except the first one into local var 'args'. + var args = new Array(i - 1); + while (--i) { + args[i - 1] = arguments[i]; + } // Let scopeFunc be the last argument and pop it so that args now only contain the table arguments. + scopeFunc = args.pop(); + var tables = flatten(args); // Support using array as middle argument, or a mix of arrays and non-arrays. + return [mode, tables, scopeFunc]; + } + + this._transaction = function (mode, tables, scopeFunc) { + var parentTransaction = PSD.trans; + // Check if parent transactions is bound to this db instance, and if caller wants to reuse it + if (!parentTransaction || parentTransaction.db !== db || mode.indexOf('!') !== -1) parentTransaction = null; + var onlyIfCompatible = mode.indexOf('?') !== -1; + mode = mode.replace('!', '').replace('?', ''); // Ok. Will change arguments[0] as well but we wont touch arguments henceforth. + + try { + // + // Get storeNames from arguments. Either through given table instances, or through given table names. + // + var storeNames = tables.map(function (table) { + var storeName = table instanceof Table ? table.name : table; + if (typeof storeName !== 'string') throw new TypeError("Invalid table argument to Dexie.transaction(). Only Table or String are allowed"); + return storeName; + }); + + // + // Resolve mode. Allow shortcuts "r" and "rw". + // + if (mode == "r" || mode == READONLY) mode = READONLY;else if (mode == "rw" || mode == READWRITE) mode = READWRITE;else throw new exceptions.InvalidArgument("Invalid transaction mode: " + mode); + + if (parentTransaction) { + // Basic checks + if (parentTransaction.mode === READONLY && mode === READWRITE) { + if (onlyIfCompatible) { + // Spawn new transaction instead. + parentTransaction = null; + } else throw new exceptions.SubTransaction("Cannot enter a sub-transaction with READWRITE mode when parent transaction is READONLY"); + } + if (parentTransaction) { + storeNames.forEach(function (storeName) { + if (parentTransaction && parentTransaction.storeNames.indexOf(storeName) === -1) { + if (onlyIfCompatible) { + // Spawn new transaction instead. + parentTransaction = null; + } else throw new exceptions.SubTransaction("Table " + storeName + " not included in parent transaction."); + } + }); + } + } + } catch (e) { + return parentTransaction ? parentTransaction._promise(null, function (_, reject) { + reject(e); + }) : rejection(e); + } + // If this is a sub-transaction, lock the parent and then launch the sub-transaction. + return parentTransaction ? parentTransaction._promise(mode, enterTransactionScope, "lock") : db._whenReady(enterTransactionScope); + + function enterTransactionScope() { + return Promise.resolve().then(function () { + // Keep a pointer to last non-transactional PSD to use if someone calls Dexie.ignoreTransaction(). + var transless = PSD.transless || PSD; + // Our transaction. + //return new Promise((resolve, reject) => { + var trans = db._createTransaction(mode, storeNames, globalSchema, parentTransaction); + // Let the transaction instance be part of a Promise-specific data (PSD) value. + var zoneProps = { + trans: trans, + transless: transless + }; + + if (parentTransaction) { + // Emulate transaction commit awareness for inner transaction (must 'commit' when the inner transaction has no more operations ongoing) + trans.idbtrans = parentTransaction.idbtrans; + } else { + trans.create(); // Create the backend transaction so that complete() or error() will trigger even if no operation is made upon it. + } + + // Support for native async await. + if (scopeFunc.constructor === AsyncFunction) { + incrementExpectedAwaits(); + } + + var returnValue; + var promiseFollowed = Promise.follow(function () { + // Finally, call the scope function with our table and transaction arguments. + returnValue = scopeFunc.call(trans, trans); + if (returnValue) { + if (returnValue.constructor === NativePromise) { + var decrementor = decrementExpectedAwaits.bind(null, null); + returnValue.then(decrementor, decrementor); + } else if (typeof returnValue.next === 'function' && typeof returnValue.throw === 'function') { + // scopeFunc returned an iterator with throw-support. Handle yield as await. + returnValue = awaitIterator(returnValue); + } + } + }, zoneProps); + return (returnValue && typeof returnValue.then === 'function' ? + // Promise returned. User uses promise-style transactions. + Promise.resolve(returnValue).then(function (x) { + return trans.active ? x // Transaction still active. Continue. + : rejection(new exceptions.PrematureCommit("Transaction committed too early. See http://bit.ly/2eVASrf")); + }) + // No promise returned. Wait for all outstanding promises before continuing. + : promiseFollowed.then(function () { + return returnValue; + })).then(function (x) { + // sub transactions don't react to idbtrans.oncomplete. We must trigger a completion: + if (parentTransaction) trans._resolve(); + // wait for trans._completion + // (if root transaction, this means 'complete' event. If sub-transaction, we've just fired it ourselves) + return trans._completion.then(function () { + return x; + }); + }).catch(function (e) { + trans._reject(e); // Yes, above then-handler were maybe not called because of an unhandled rejection in scopeFunc! + return rejection(e); + }); + }); + } + }; + + this.table = function (tableName) { + /// + if (fake && autoSchema) return new Table(tableName); + if (!hasOwn(allTables, tableName)) { + throw new exceptions.InvalidTable('Table ' + tableName + ' does not exist'); + } + return allTables[tableName]; + }; + + // + // + // + // Table Class + // + // + // + function Table(name, tableSchema, optionalTrans) { + /// + this.name = name; + this.schema = tableSchema; + this._tx = optionalTrans; + this.hook = allTables[name] ? allTables[name].hook : Events(null, { + "creating": [hookCreatingChain, nop], + "reading": [pureFunctionChain, mirror], + "updating": [hookUpdatingChain, nop], + "deleting": [hookDeletingChain, nop] + }); + } + + function BulkErrorHandlerCatchAll(errorList, done, supportHooks) { + return (supportHooks ? hookedEventRejectHandler : eventRejectHandler)(function (e) { + errorList.push(e); + done && done(); + }); + } + + function bulkDelete(idbstore, trans, keysOrTuples, hasDeleteHook, deletingHook) { + // If hasDeleteHook, keysOrTuples must be an array of tuples: [[key1, value2],[key2,value2],...], + // else keysOrTuples must be just an array of keys: [key1, key2, ...]. + return new Promise(function (resolve, reject) { + var len = keysOrTuples.length, + lastItem = len - 1; + if (len === 0) return resolve(); + if (!hasDeleteHook) { + for (var i = 0; i < len; ++i) { + var req = idbstore.delete(keysOrTuples[i]); + req.onerror = eventRejectHandler(reject); + if (i === lastItem) req.onsuccess = wrap(function () { + return resolve(); + }); + } + } else { + var hookCtx, + errorHandler = hookedEventRejectHandler(reject), + successHandler = hookedEventSuccessHandler(null); + tryCatch(function () { + for (var i = 0; i < len; ++i) { + hookCtx = { onsuccess: null, onerror: null }; + var tuple = keysOrTuples[i]; + deletingHook.call(hookCtx, tuple[0], tuple[1], trans); + var req = idbstore.delete(tuple[0]); + req._hookCtx = hookCtx; + req.onerror = errorHandler; + if (i === lastItem) req.onsuccess = hookedEventSuccessHandler(resolve);else req.onsuccess = successHandler; + } + }, function (err) { + hookCtx.onerror && hookCtx.onerror(err); + throw err; + }); + } + }); + } + + props(Table.prototype, { + + // + // Table Protected Methods + // + + _trans: function getTransaction(mode, fn, writeLocked) { + var trans = this._tx || PSD.trans; + return trans && trans.db === db ? trans === PSD.trans ? trans._promise(mode, fn, writeLocked) : newScope(function () { + return trans._promise(mode, fn, writeLocked); + }, { trans: trans, transless: PSD.transless || PSD }) : tempTransaction(mode, [this.name], fn); + }, + _idbstore: function getIDBObjectStore(mode, fn, writeLocked) { + if (fake) return new Promise(fn); // Simplify the work for Intellisense/Code completion. + var tableName = this.name; + function supplyIdbStore(resolve, reject, trans) { + if (trans.storeNames.indexOf(tableName) === -1) throw new exceptions.NotFound("Table" + tableName + " not part of transaction"); + return fn(resolve, reject, trans.idbtrans.objectStore(tableName), trans); + } + return this._trans(mode, supplyIdbStore, writeLocked); + }, + + // + // Table Public Methods + // + get: function (keyOrCrit, cb) { + if (keyOrCrit && keyOrCrit.constructor === Object) return this.where(keyOrCrit).first(cb); + var self = this; + return this._idbstore(READONLY, function (resolve, reject, idbstore) { + fake && resolve(self.schema.instanceTemplate); + var req = idbstore.get(keyOrCrit); + req.onerror = eventRejectHandler(reject); + req.onsuccess = wrap(function () { + resolve(self.hook.reading.fire(req.result)); + }, reject); + }).then(cb); + }, + where: function (indexOrCrit) { + if (typeof indexOrCrit === 'string') return new WhereClause(this, indexOrCrit); + if (isArray(indexOrCrit)) return new WhereClause(this, '[' + indexOrCrit.join('+') + ']'); + // indexOrCrit is an object map of {[keyPath]:value} + var keyPaths = keys(indexOrCrit); + if (keyPaths.length === 1) + // Only one critera. This was the easy case: + return this.where(keyPaths[0]).equals(indexOrCrit[keyPaths[0]]); + + // Multiple criterias. + // Let's try finding a compound index that matches all keyPaths in + // arbritary order: + var compoundIndex = this.schema.indexes.concat(this.schema.primKey).filter(function (ix) { + return ix.compound && keyPaths.every(function (keyPath) { + return ix.keyPath.indexOf(keyPath) >= 0; + }) && ix.keyPath.every(function (keyPath) { + return keyPaths.indexOf(keyPath) >= 0; + }); + })[0]; + + if (compoundIndex && maxKey !== maxString) + // Cool! We found such compound index + // and this browser supports compound indexes (maxKey !== maxString)! + return this.where(compoundIndex.name).equals(compoundIndex.keyPath.map(function (kp) { + return indexOrCrit[kp]; + })); + + if (!compoundIndex) console.warn('The query ' + JSON.stringify(indexOrCrit) + ' on ' + this.name + ' would benefit of a ' + ('compound index [' + keyPaths.join('+') + ']')); + + // Ok, now let's fallback to finding at least one matching index + // and filter the rest. + var idxByName = this.schema.idxByName; + var simpleIndex = keyPaths.reduce(function (r, keyPath) { + return [r[0] || idxByName[keyPath], r[0] || !idxByName[keyPath] ? combine(r[1], function (x) { + return '' + getByKeyPath(x, keyPath) == '' + indexOrCrit[keyPath]; + }) : r[1]]; + }, [null, null]); + + var idx = simpleIndex[0]; + return idx ? this.where(idx.name).equals(indexOrCrit[idx.keyPath]).filter(simpleIndex[1]) : compoundIndex ? this.filter(simpleIndex[1]) : // Has compound but browser bad. Allow filter. + this.where(keyPaths).equals(''); // No index at all. Fail lazily. + }, + count: function (cb) { + return this.toCollection().count(cb); + }, + offset: function (offset) { + return this.toCollection().offset(offset); + }, + limit: function (numRows) { + return this.toCollection().limit(numRows); + }, + reverse: function () { + return this.toCollection().reverse(); + }, + filter: function (filterFunction) { + return this.toCollection().and(filterFunction); + }, + each: function (fn) { + return this.toCollection().each(fn); + }, + toArray: function (cb) { + return this.toCollection().toArray(cb); + }, + orderBy: function (index) { + return new Collection(new WhereClause(this, isArray(index) ? '[' + index.join('+') + ']' : index)); + }, + + toCollection: function () { + return new Collection(new WhereClause(this)); + }, + + mapToClass: function (constructor, structure) { + /// + /// Map table to a javascript constructor function. Objects returned from the database will be instances of this class, making + /// it possible to the instanceOf operator as well as extending the class using constructor.prototype.method = function(){...}. + /// + /// Constructor function representing the class. + /// Helps IDE code completion by knowing the members that objects contain and not just the indexes. Also + /// know what type each member has. Example: {name: String, emailAddresses: [String], password} + this.schema.mappedClass = constructor; + var instanceTemplate = Object.create(constructor.prototype); + if (structure) { + // structure and instanceTemplate is for IDE code competion only while constructor.prototype is for actual inheritance. + applyStructure(instanceTemplate, structure); + } + this.schema.instanceTemplate = instanceTemplate; + + // Now, subscribe to the when("reading") event to make all objects that come out from this table inherit from given class + // no matter which method to use for reading (Table.get() or Table.where(...)... ) + var readHook = function (obj) { + if (!obj) return obj; // No valid object. (Value is null). Return as is. + // Create a new object that derives from constructor: + var res = Object.create(constructor.prototype); + // Clone members: + for (var m in obj) { + if (hasOwn(obj, m)) try { + res[m] = obj[m]; + } catch (_) {} + }return res; + }; + + if (this.schema.readHook) { + this.hook.reading.unsubscribe(this.schema.readHook); + } + this.schema.readHook = readHook; + this.hook("reading", readHook); + return constructor; + }, + defineClass: function (structure) { + /// + /// Define all members of the class that represents the table. This will help code completion of when objects are read from the database + /// as well as making it possible to extend the prototype of the returned constructor function. + /// + /// Helps IDE code completion by knowing the members that objects contain and not just the indexes. Also + /// know what type each member has. Example: {name: String, emailAddresses: [String], properties: {shoeSize: Number}} + return this.mapToClass(Dexie.defineClass(structure), structure); + }, + + bulkDelete: function (keys$$1) { + if (this.hook.deleting.fire === nop) { + return this._idbstore(READWRITE, function (resolve, reject, idbstore, trans) { + resolve(bulkDelete(idbstore, trans, keys$$1, false, nop)); + }); + } else { + return this.where(':id').anyOf(keys$$1).delete().then(function () {}); // Resolve with undefined. + } + }, + bulkPut: function (objects, keys$$1) { + var _this = this; + + return this._idbstore(READWRITE, function (resolve, reject, idbstore) { + if (!idbstore.keyPath && !_this.schema.primKey.auto && !keys$$1) throw new exceptions.InvalidArgument("bulkPut() with non-inbound keys requires keys array in second argument"); + if (idbstore.keyPath && keys$$1) throw new exceptions.InvalidArgument("bulkPut(): keys argument invalid on tables with inbound keys"); + if (keys$$1 && keys$$1.length !== objects.length) throw new exceptions.InvalidArgument("Arguments objects and keys must have the same length"); + if (objects.length === 0) return resolve(); // Caller provided empty list. + var done = function (result) { + if (errorList.length === 0) resolve(result);else reject(new BulkError(_this.name + '.bulkPut(): ' + errorList.length + ' of ' + numObjs + ' operations failed', errorList)); + }; + var req, + errorList = [], + errorHandler, + numObjs = objects.length, + table = _this; + if (_this.hook.creating.fire === nop && _this.hook.updating.fire === nop) { + // + // Standard Bulk (no 'creating' or 'updating' hooks to care about) + // + errorHandler = BulkErrorHandlerCatchAll(errorList); + for (var i = 0, l = objects.length; i < l; ++i) { + req = keys$$1 ? idbstore.put(objects[i], keys$$1[i]) : idbstore.put(objects[i]); + req.onerror = errorHandler; + } + // Only need to catch success or error on the last operation + // according to the IDB spec. + req.onerror = BulkErrorHandlerCatchAll(errorList, done); + req.onsuccess = eventSuccessHandler(done); + } else { + var effectiveKeys = keys$$1 || idbstore.keyPath && objects.map(function (o) { + return getByKeyPath(o, idbstore.keyPath); + }); + // Generate map of {[key]: object} + var objectLookup = effectiveKeys && arrayToObject(effectiveKeys, function (key, i) { + return key != null && [key, objects[i]]; + }); + var promise = !effectiveKeys ? + + // Auto-incremented key-less objects only without any keys argument. + table.bulkAdd(objects) : + + // Keys provided. Either as inbound in provided objects, or as a keys argument. + // Begin with updating those that exists in DB: + table.where(':id').anyOf(effectiveKeys.filter(function (key) { + return key != null; + })).modify(function () { + this.value = objectLookup[this.primKey]; + objectLookup[this.primKey] = null; // Mark as "don't add this" + }).catch(ModifyError, function (e) { + errorList = e.failures; // No need to concat here. These are the first errors added. + }).then(function () { + // Now, let's examine which items didnt exist so we can add them: + var objsToAdd = [], + keysToAdd = keys$$1 && []; + // Iterate backwards. Why? Because if same key was used twice, just add the last one. + for (var i = effectiveKeys.length - 1; i >= 0; --i) { + var key = effectiveKeys[i]; + if (key == null || objectLookup[key]) { + objsToAdd.push(objects[i]); + keys$$1 && keysToAdd.push(key); + if (key != null) objectLookup[key] = null; // Mark as "dont add again" + } + } + // The items are in reverse order so reverse them before adding. + // Could be important in order to get auto-incremented keys the way the caller + // would expect. Could have used unshift instead of push()/reverse(), + // but: http://jsperf.com/unshift-vs-reverse + objsToAdd.reverse(); + keys$$1 && keysToAdd.reverse(); + return table.bulkAdd(objsToAdd, keysToAdd); + }).then(function (lastAddedKey) { + // Resolve with key of the last object in given arguments to bulkPut(): + var lastEffectiveKey = effectiveKeys[effectiveKeys.length - 1]; // Key was provided. + return lastEffectiveKey != null ? lastEffectiveKey : lastAddedKey; + }); + + promise.then(done).catch(BulkError, function (e) { + // Concat failure from ModifyError and reject using our 'done' method. + errorList = errorList.concat(e.failures); + done(); + }).catch(reject); + } + }, "locked"); // If called from transaction scope, lock transaction til all steps are done. + }, + bulkAdd: function (objects, keys$$1) { + var self = this, + creatingHook = this.hook.creating.fire; + return this._idbstore(READWRITE, function (resolve, reject, idbstore, trans) { + if (!idbstore.keyPath && !self.schema.primKey.auto && !keys$$1) throw new exceptions.InvalidArgument("bulkAdd() with non-inbound keys requires keys array in second argument"); + if (idbstore.keyPath && keys$$1) throw new exceptions.InvalidArgument("bulkAdd(): keys argument invalid on tables with inbound keys"); + if (keys$$1 && keys$$1.length !== objects.length) throw new exceptions.InvalidArgument("Arguments objects and keys must have the same length"); + if (objects.length === 0) return resolve(); // Caller provided empty list. + function done(result) { + if (errorList.length === 0) resolve(result);else reject(new BulkError(self.name + '.bulkAdd(): ' + errorList.length + ' of ' + numObjs + ' operations failed', errorList)); + } + var req, + errorList = [], + errorHandler, + successHandler, + numObjs = objects.length; + if (creatingHook !== nop) { + // + // There are subscribers to hook('creating') + // Must behave as documented. + // + var keyPath = idbstore.keyPath, + hookCtx; + errorHandler = BulkErrorHandlerCatchAll(errorList, null, true); + successHandler = hookedEventSuccessHandler(null); + + tryCatch(function () { + for (var i = 0, l = objects.length; i < l; ++i) { + hookCtx = { onerror: null, onsuccess: null }; + var key = keys$$1 && keys$$1[i]; + var obj = objects[i], + effectiveKey = keys$$1 ? key : keyPath ? getByKeyPath(obj, keyPath) : undefined, + keyToUse = creatingHook.call(hookCtx, effectiveKey, obj, trans); + if (effectiveKey == null && keyToUse != null) { + if (keyPath) { + obj = deepClone(obj); + setByKeyPath(obj, keyPath, keyToUse); + } else { + key = keyToUse; + } + } + req = key != null ? idbstore.add(obj, key) : idbstore.add(obj); + req._hookCtx = hookCtx; + if (i < l - 1) { + req.onerror = errorHandler; + if (hookCtx.onsuccess) req.onsuccess = successHandler; + } + } + }, function (err) { + hookCtx.onerror && hookCtx.onerror(err); + throw err; + }); + + req.onerror = BulkErrorHandlerCatchAll(errorList, done, true); + req.onsuccess = hookedEventSuccessHandler(done); + } else { + // + // Standard Bulk (no 'creating' hook to care about) + // + errorHandler = BulkErrorHandlerCatchAll(errorList); + for (var i = 0, l = objects.length; i < l; ++i) { + req = keys$$1 ? idbstore.add(objects[i], keys$$1[i]) : idbstore.add(objects[i]); + req.onerror = errorHandler; + } + // Only need to catch success or error on the last operation + // according to the IDB spec. + req.onerror = BulkErrorHandlerCatchAll(errorList, done); + req.onsuccess = eventSuccessHandler(done); + } + }); + }, + add: function (obj, key) { + /// + /// Add an object to the database. In case an object with same primary key already exists, the object will not be added. + /// + /// A javascript object to insert + /// Primary key + var creatingHook = this.hook.creating.fire; + return this._idbstore(READWRITE, function (resolve, reject, idbstore, trans) { + var hookCtx = { onsuccess: null, onerror: null }; + if (creatingHook !== nop) { + var effectiveKey = key != null ? key : idbstore.keyPath ? getByKeyPath(obj, idbstore.keyPath) : undefined; + var keyToUse = creatingHook.call(hookCtx, effectiveKey, obj, trans); // Allow subscribers to when("creating") to generate the key. + if (effectiveKey == null && keyToUse != null) { + // Using "==" and "!=" to check for either null or undefined! + if (idbstore.keyPath) setByKeyPath(obj, idbstore.keyPath, keyToUse);else key = keyToUse; + } + } + try { + var req = key != null ? idbstore.add(obj, key) : idbstore.add(obj); + req._hookCtx = hookCtx; + req.onerror = hookedEventRejectHandler(reject); + req.onsuccess = hookedEventSuccessHandler(function (result) { + // TODO: Remove these two lines in next major release (2.0?) + // It's no good practice to have side effects on provided parameters + var keyPath = idbstore.keyPath; + if (keyPath) setByKeyPath(obj, keyPath, result); + resolve(result); + }); + } catch (e) { + if (hookCtx.onerror) hookCtx.onerror(e); + throw e; + } + }); + }, + + put: function (obj, key) { + var _this2 = this; + + /// + /// Add an object to the database but in case an object with same primary key alread exists, the existing one will get updated. + /// + /// A javascript object to insert or update + /// Primary key + var creatingHook = this.hook.creating.fire, + updatingHook = this.hook.updating.fire; + if (creatingHook !== nop || updatingHook !== nop) { + // + // People listens to when("creating") or when("updating") events! + // We must know whether the put operation results in an CREATE or UPDATE. + // + var keyPath = this.schema.primKey.keyPath; + var effectiveKey = key !== undefined ? key : keyPath && getByKeyPath(obj, keyPath); + if (effectiveKey == null) // "== null" means checking for either null or undefined. + return this.add(obj); + + // Since key is optional, make sure we get it from obj if not provided + + // Primary key exist. Lock transaction and try modifying existing. If nothing modified, call add(). + // clone obj before this async call. If caller modifies obj the line after put(), the IDB spec requires that it should not affect operation. + obj = deepClone(obj); + return this._trans(READWRITE, function () { + return _this2.where(":id").equals(effectiveKey).modify(function () { + // Replace extisting value with our object + // CRUD event firing handled in Collection.modify() + this.value = obj; + }).then(function (count) { + return count === 0 ? _this2.add(obj, key) : effectiveKey; + }); + }, "locked"); // Lock needed because operation is splitted into modify() and add(). + } else { + // Use the standard IDB put() method. + return this._idbstore(READWRITE, function (resolve, reject, idbstore) { + var req = key !== undefined ? idbstore.put(obj, key) : idbstore.put(obj); + req.onerror = eventRejectHandler(reject); + req.onsuccess = wrap(function (ev) { + var keyPath = idbstore.keyPath; + if (keyPath) setByKeyPath(obj, keyPath, ev.target.result); + resolve(req.result); + }); + }); + } + }, + + 'delete': function (key) { + /// Primary key of the object to delete + if (this.hook.deleting.subscribers.length) { + // People listens to when("deleting") event. Must implement delete using Collection.delete() that will + // call the CRUD event. Only Collection.delete() will know whether an object was actually deleted. + return this.where(":id").equals(key).delete(); + } else { + // No one listens. Use standard IDB delete() method. + return this._idbstore(READWRITE, function (resolve, reject, idbstore) { + var req = idbstore.delete(key); + req.onerror = eventRejectHandler(reject); + req.onsuccess = wrap(function () { + resolve(req.result); + }); + }); + } + }, + + clear: function () { + if (this.hook.deleting.subscribers.length) { + // People listens to when("deleting") event. Must implement delete using Collection.delete() that will + // call the CRUD event. Only Collection.delete() will knows which objects that are actually deleted. + return this.toCollection().delete(); + } else { + return this._idbstore(READWRITE, function (resolve, reject, idbstore) { + var req = idbstore.clear(); + req.onerror = eventRejectHandler(reject); + req.onsuccess = wrap(function () { + resolve(req.result); + }); + }); + } + }, + + update: function (keyOrObject, modifications) { + if (typeof modifications !== 'object' || isArray(modifications)) throw new exceptions.InvalidArgument("Modifications must be an object."); + if (typeof keyOrObject === 'object' && !isArray(keyOrObject)) { + // object to modify. Also modify given object with the modifications: + keys(modifications).forEach(function (keyPath) { + setByKeyPath(keyOrObject, keyPath, modifications[keyPath]); + }); + var key = getByKeyPath(keyOrObject, this.schema.primKey.keyPath); + if (key === undefined) return rejection(new exceptions.InvalidArgument("Given object does not contain its primary key")); + return this.where(":id").equals(key).modify(modifications); + } else { + // key to modify + return this.where(":id").equals(keyOrObject).modify(modifications); + } + } + }); + + // + // + // + // Transaction Class + // + // + // + function Transaction(mode, storeNames, dbschema, parent) { + var _this3 = this; + + /// + /// Transaction class. Represents a database transaction. All operations on db goes through a Transaction. + /// + /// Any of "readwrite" or "readonly" + /// Array of table names to operate on + this.db = db; + this.mode = mode; + this.storeNames = storeNames; + this.idbtrans = null; + this.on = Events(this, "complete", "error", "abort"); + this.parent = parent || null; + this.active = true; + this._reculock = 0; + this._blockedFuncs = []; + this._resolve = null; + this._reject = null; + this._waitingFor = null; + this._waitingQueue = null; + this._spinCount = 0; // Just for debugging waitFor() + this._completion = new Promise(function (resolve, reject) { + _this3._resolve = resolve; + _this3._reject = reject; + }); + + this._completion.then(function () { + _this3.on.complete.fire(); + }, function (e) { + _this3.on.error.fire(e); + _this3.parent ? _this3.parent._reject(e) : _this3.active && _this3.idbtrans && _this3.idbtrans.abort(); + _this3.active = false; + return rejection(e); // Indicate we actually DO NOT catch this error. + }); + } + + props(Transaction.prototype, { + // + // Transaction Protected Methods (not required by API users, but needed internally and eventually by dexie extensions) + // + _lock: function () { + assert(!PSD.global); // Locking and unlocking reuires to be within a PSD scope. + // Temporary set all requests into a pending queue if they are called before database is ready. + ++this._reculock; // Recursive read/write lock pattern using PSD (Promise Specific Data) instead of TLS (Thread Local Storage) + if (this._reculock === 1 && !PSD.global) PSD.lockOwnerFor = this; + return this; + }, + _unlock: function () { + assert(!PSD.global); // Locking and unlocking reuires to be within a PSD scope. + if (--this._reculock === 0) { + if (!PSD.global) PSD.lockOwnerFor = null; + while (this._blockedFuncs.length > 0 && !this._locked()) { + var fnAndPSD = this._blockedFuncs.shift(); + try { + usePSD(fnAndPSD[1], fnAndPSD[0]); + } catch (e) {} + } + } + return this; + }, + _locked: function () { + // Checks if any write-lock is applied on this transaction. + // To simplify the Dexie API for extension implementations, we support recursive locks. + // This is accomplished by using "Promise Specific Data" (PSD). + // PSD data is bound to a Promise and any child Promise emitted through then() or resolve( new Promise() ). + // PSD is local to code executing on top of the call stacks of any of any code executed by Promise(): + // * callback given to the Promise() constructor (function (resolve, reject){...}) + // * callbacks given to then()/catch()/finally() methods (function (value){...}) + // If creating a new independant Promise instance from within a Promise call stack, the new Promise will derive the PSD from the call stack of the parent Promise. + // Derivation is done so that the inner PSD __proto__ points to the outer PSD. + // PSD.lockOwnerFor will point to current transaction object if the currently executing PSD scope owns the lock. + return this._reculock && PSD.lockOwnerFor !== this; + }, + create: function (idbtrans) { + var _this4 = this; + + if (!this.mode) return this; + assert(!this.idbtrans); + if (!idbtrans && !idbdb) { + switch (dbOpenError && dbOpenError.name) { + case "DatabaseClosedError": + // Errors where it is no difference whether it was caused by the user operation or an earlier call to db.open() + throw new exceptions.DatabaseClosed(dbOpenError); + case "MissingAPIError": + // Errors where it is no difference whether it was caused by the user operation or an earlier call to db.open() + throw new exceptions.MissingAPI(dbOpenError.message, dbOpenError); + default: + // Make it clear that the user operation was not what caused the error - the error had occurred earlier on db.open()! + throw new exceptions.OpenFailed(dbOpenError); + } + } + if (!this.active) throw new exceptions.TransactionInactive(); + assert(this._completion._state === null); + + idbtrans = this.idbtrans = idbtrans || idbdb.transaction(safariMultiStoreFix(this.storeNames), this.mode); + idbtrans.onerror = wrap(function (ev) { + preventDefault(ev); // Prohibit default bubbling to window.error + _this4._reject(idbtrans.error); + }); + idbtrans.onabort = wrap(function (ev) { + preventDefault(ev); + _this4.active && _this4._reject(new exceptions.Abort()); + _this4.active = false; + _this4.on("abort").fire(ev); + }); + idbtrans.oncomplete = wrap(function () { + _this4.active = false; + _this4._resolve(); + }); + return this; + }, + _promise: function (mode, fn, bWriteLock) { + var _this5 = this; + + if (mode === READWRITE && this.mode !== READWRITE) return rejection(new exceptions.ReadOnly("Transaction is readonly")); + + if (!this.active) return rejection(new exceptions.TransactionInactive()); + + if (this._locked()) { + return new Promise(function (resolve, reject) { + _this5._blockedFuncs.push([function () { + _this5._promise(mode, fn, bWriteLock).then(resolve, reject); + }, PSD]); + }); + } else if (bWriteLock) { + return newScope(function () { + var p = new Promise(function (resolve, reject) { + _this5._lock(); + var rv = fn(resolve, reject, _this5); + if (rv && rv.then) rv.then(resolve, reject); + }); + p.finally(function () { + return _this5._unlock(); + }); + p._lib = true; + return p; + }); + } else { + var p = new Promise(function (resolve, reject) { + var rv = fn(resolve, reject, _this5); + if (rv && rv.then) rv.then(resolve, reject); + }); + p._lib = true; + return p; + } + }, + + _root: function () { + return this.parent ? this.parent._root() : this; + }, + + waitFor: function (promise) { + // Always operate on the root transaction (in case this is a sub stransaction) + var root = this._root(); + // For stability reasons, convert parameter to promise no matter what type is passed to waitFor(). + // (We must be able to call .then() on it.) + promise = Promise.resolve(promise); + if (root._waitingFor) { + // Already called waitFor(). Wait for both to complete. + root._waitingFor = root._waitingFor.then(function () { + return promise; + }); + } else { + // We're not in waiting state. Start waiting state. + root._waitingFor = promise; + root._waitingQueue = []; + // Start interacting with indexedDB until promise completes: + var store = root.idbtrans.objectStore(root.storeNames[0]); + (function spin() { + ++root._spinCount; // For debugging only + while (root._waitingQueue.length) { + root._waitingQueue.shift()(); + }if (root._waitingFor) store.get(-Infinity).onsuccess = spin; + })(); + } + var currentWaitPromise = root._waitingFor; + return new Promise(function (resolve, reject) { + promise.then(function (res) { + return root._waitingQueue.push(wrap(resolve.bind(null, res))); + }, function (err) { + return root._waitingQueue.push(wrap(reject.bind(null, err))); + }).finally(function () { + if (root._waitingFor === currentWaitPromise) { + // No one added a wait after us. Safe to stop the spinning. + root._waitingFor = null; + } + }); + }); + }, + + + // + // Transaction Public Properties and Methods + // + abort: function () { + this.active && this._reject(new exceptions.Abort()); + this.active = false; + }, + + tables: { + get: deprecated("Transaction.tables", function () { + return allTables; + }) + }, + + table: function (name) { + var table = db.table(name); // Don't check that table is part of transaction. It must fail lazily! + return new Table(name, table.schema, this); + } + }); + + // + // + // + // WhereClause + // + // + // + function WhereClause(table, index, orCollection) { + /// + /// + /// + this._ctx = { + table: table, + index: index === ":id" ? null : index, + or: orCollection + }; + } + + props(WhereClause.prototype, function () { + + // WhereClause private methods + + function fail(collectionOrWhereClause, err, T) { + var collection = collectionOrWhereClause instanceof WhereClause ? new Collection(collectionOrWhereClause) : collectionOrWhereClause; + + collection._ctx.error = T ? new T(err) : new TypeError(err); + return collection; + } + + function emptyCollection(whereClause) { + return new Collection(whereClause, function () { + return IDBKeyRange.only(""); + }).limit(0); + } + + function upperFactory(dir) { + return dir === "next" ? function (s) { + return s.toUpperCase(); + } : function (s) { + return s.toLowerCase(); + }; + } + function lowerFactory(dir) { + return dir === "next" ? function (s) { + return s.toLowerCase(); + } : function (s) { + return s.toUpperCase(); + }; + } + function nextCasing(key, lowerKey, upperNeedle, lowerNeedle, cmp, dir) { + var length = Math.min(key.length, lowerNeedle.length); + var llp = -1; + for (var i = 0; i < length; ++i) { + var lwrKeyChar = lowerKey[i]; + if (lwrKeyChar !== lowerNeedle[i]) { + if (cmp(key[i], upperNeedle[i]) < 0) return key.substr(0, i) + upperNeedle[i] + upperNeedle.substr(i + 1); + if (cmp(key[i], lowerNeedle[i]) < 0) return key.substr(0, i) + lowerNeedle[i] + upperNeedle.substr(i + 1); + if (llp >= 0) return key.substr(0, llp) + lowerKey[llp] + upperNeedle.substr(llp + 1); + return null; + } + if (cmp(key[i], lwrKeyChar) < 0) llp = i; + } + if (length < lowerNeedle.length && dir === "next") return key + upperNeedle.substr(key.length); + if (length < key.length && dir === "prev") return key.substr(0, upperNeedle.length); + return llp < 0 ? null : key.substr(0, llp) + lowerNeedle[llp] + upperNeedle.substr(llp + 1); + } + + function addIgnoreCaseAlgorithm(whereClause, match, needles, suffix) { + /// + var upper, + lower, + compare, + upperNeedles, + lowerNeedles, + direction, + nextKeySuffix, + needlesLen = needles.length; + if (!needles.every(function (s) { + return typeof s === 'string'; + })) { + return fail(whereClause, STRING_EXPECTED); + } + function initDirection(dir) { + upper = upperFactory(dir); + lower = lowerFactory(dir); + compare = dir === "next" ? simpleCompare : simpleCompareReverse; + var needleBounds = needles.map(function (needle) { + return { lower: lower(needle), upper: upper(needle) }; + }).sort(function (a, b) { + return compare(a.lower, b.lower); + }); + upperNeedles = needleBounds.map(function (nb) { + return nb.upper; + }); + lowerNeedles = needleBounds.map(function (nb) { + return nb.lower; + }); + direction = dir; + nextKeySuffix = dir === "next" ? "" : suffix; + } + initDirection("next"); + + var c = new Collection(whereClause, function () { + return IDBKeyRange.bound(upperNeedles[0], lowerNeedles[needlesLen - 1] + suffix); + }); + + c._ondirectionchange = function (direction) { + // This event onlys occur before filter is called the first time. + initDirection(direction); + }; + + var firstPossibleNeedle = 0; + + c._addAlgorithm(function (cursor, advance, resolve) { + /// + /// + /// + var key = cursor.key; + if (typeof key !== 'string') return false; + var lowerKey = lower(key); + if (match(lowerKey, lowerNeedles, firstPossibleNeedle)) { + return true; + } else { + var lowestPossibleCasing = null; + for (var i = firstPossibleNeedle; i < needlesLen; ++i) { + var casing = nextCasing(key, lowerKey, upperNeedles[i], lowerNeedles[i], compare, direction); + if (casing === null && lowestPossibleCasing === null) firstPossibleNeedle = i + 1;else if (lowestPossibleCasing === null || compare(lowestPossibleCasing, casing) > 0) { + lowestPossibleCasing = casing; + } + } + if (lowestPossibleCasing !== null) { + advance(function () { + cursor.continue(lowestPossibleCasing + nextKeySuffix); + }); + } else { + advance(resolve); + } + return false; + } + }); + return c; + } + + // + // WhereClause public methods + // + return { + between: function (lower, upper, includeLower, includeUpper) { + /// + /// Filter out records whose where-field lays between given lower and upper values. Applies to Strings, Numbers and Dates. + /// + /// + /// + /// Whether items that equals lower should be included. Default true. + /// Whether items that equals upper should be included. Default false. + /// + includeLower = includeLower !== false; // Default to true + includeUpper = includeUpper === true; // Default to false + try { + if (cmp(lower, upper) > 0 || cmp(lower, upper) === 0 && (includeLower || includeUpper) && !(includeLower && includeUpper)) return emptyCollection(this); // Workaround for idiotic W3C Specification that DataError must be thrown if lower > upper. The natural result would be to return an empty collection. + return new Collection(this, function () { + return IDBKeyRange.bound(lower, upper, !includeLower, !includeUpper); + }); + } catch (e) { + return fail(this, INVALID_KEY_ARGUMENT); + } + }, + equals: function (value) { + return new Collection(this, function () { + return IDBKeyRange.only(value); + }); + }, + above: function (value) { + return new Collection(this, function () { + return IDBKeyRange.lowerBound(value, true); + }); + }, + aboveOrEqual: function (value) { + return new Collection(this, function () { + return IDBKeyRange.lowerBound(value); + }); + }, + below: function (value) { + return new Collection(this, function () { + return IDBKeyRange.upperBound(value, true); + }); + }, + belowOrEqual: function (value) { + return new Collection(this, function () { + return IDBKeyRange.upperBound(value); + }); + }, + startsWith: function (str) { + /// + if (typeof str !== 'string') return fail(this, STRING_EXPECTED); + return this.between(str, str + maxString, true, true); + }, + startsWithIgnoreCase: function (str) { + /// + if (str === "") return this.startsWith(str); + return addIgnoreCaseAlgorithm(this, function (x, a) { + return x.indexOf(a[0]) === 0; + }, [str], maxString); + }, + equalsIgnoreCase: function (str) { + /// + return addIgnoreCaseAlgorithm(this, function (x, a) { + return x === a[0]; + }, [str], ""); + }, + anyOfIgnoreCase: function () { + var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + if (set.length === 0) return emptyCollection(this); + return addIgnoreCaseAlgorithm(this, function (x, a) { + return a.indexOf(x) !== -1; + }, set, ""); + }, + startsWithAnyOfIgnoreCase: function () { + var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + if (set.length === 0) return emptyCollection(this); + return addIgnoreCaseAlgorithm(this, function (x, a) { + return a.some(function (n) { + return x.indexOf(n) === 0; + }); + }, set, maxString); + }, + anyOf: function () { + var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + var compare = ascending; + try { + set.sort(compare); + } catch (e) { + return fail(this, INVALID_KEY_ARGUMENT); + } + if (set.length === 0) return emptyCollection(this); + var c = new Collection(this, function () { + return IDBKeyRange.bound(set[0], set[set.length - 1]); + }); + + c._ondirectionchange = function (direction) { + compare = direction === "next" ? ascending : descending; + set.sort(compare); + }; + var i = 0; + c._addAlgorithm(function (cursor, advance, resolve) { + var key = cursor.key; + while (compare(key, set[i]) > 0) { + // The cursor has passed beyond this key. Check next. + ++i; + if (i === set.length) { + // There is no next. Stop searching. + advance(resolve); + return false; + } + } + if (compare(key, set[i]) === 0) { + // The current cursor value should be included and we should continue a single step in case next item has the same key or possibly our next key in set. + return true; + } else { + // cursor.key not yet at set[i]. Forward cursor to the next key to hunt for. + advance(function () { + cursor.continue(set[i]); + }); + return false; + } + }); + return c; + }, + + notEqual: function (value) { + return this.inAnyRange([[minKey, value], [value, maxKey]], { includeLowers: false, includeUppers: false }); + }, + + noneOf: function () { + var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + if (set.length === 0) return new Collection(this); // Return entire collection. + try { + set.sort(ascending); + } catch (e) { + return fail(this, INVALID_KEY_ARGUMENT); + } + // Transform ["a","b","c"] to a set of ranges for between/above/below: [[minKey,"a"], ["a","b"], ["b","c"], ["c",maxKey]] + var ranges = set.reduce(function (res, val) { + return res ? res.concat([[res[res.length - 1][1], val]]) : [[minKey, val]]; + }, null); + ranges.push([set[set.length - 1], maxKey]); + return this.inAnyRange(ranges, { includeLowers: false, includeUppers: false }); + }, + + /** Filter out values withing given set of ranges. + * Example, give children and elders a rebate of 50%: + * + * db.friends.where('age').inAnyRange([[0,18],[65,Infinity]]).modify({Rebate: 1/2}); + * + * @param {(string|number|Date|Array)[][]} ranges + * @param {{includeLowers: boolean, includeUppers: boolean}} options + */ + inAnyRange: function (ranges, options) { + if (ranges.length === 0) return emptyCollection(this); + if (!ranges.every(function (range) { + return range[0] !== undefined && range[1] !== undefined && ascending(range[0], range[1]) <= 0; + })) { + return fail(this, "First argument to inAnyRange() must be an Array of two-value Arrays [lower,upper] where upper must not be lower than lower", exceptions.InvalidArgument); + } + var includeLowers = !options || options.includeLowers !== false; // Default to true + var includeUppers = options && options.includeUppers === true; // Default to false + + function addRange(ranges, newRange) { + for (var i = 0, l = ranges.length; i < l; ++i) { + var range = ranges[i]; + if (cmp(newRange[0], range[1]) < 0 && cmp(newRange[1], range[0]) > 0) { + range[0] = min(range[0], newRange[0]); + range[1] = max(range[1], newRange[1]); + break; + } + } + if (i === l) ranges.push(newRange); + return ranges; + } + + var sortDirection = ascending; + function rangeSorter(a, b) { + return sortDirection(a[0], b[0]); + } + + // Join overlapping ranges + var set; + try { + set = ranges.reduce(addRange, []); + set.sort(rangeSorter); + } catch (ex) { + return fail(this, INVALID_KEY_ARGUMENT); + } + + var i = 0; + var keyIsBeyondCurrentEntry = includeUppers ? function (key) { + return ascending(key, set[i][1]) > 0; + } : function (key) { + return ascending(key, set[i][1]) >= 0; + }; + + var keyIsBeforeCurrentEntry = includeLowers ? function (key) { + return descending(key, set[i][0]) > 0; + } : function (key) { + return descending(key, set[i][0]) >= 0; + }; + + function keyWithinCurrentRange(key) { + return !keyIsBeyondCurrentEntry(key) && !keyIsBeforeCurrentEntry(key); + } + + var checkKey = keyIsBeyondCurrentEntry; + + var c = new Collection(this, function () { + return IDBKeyRange.bound(set[0][0], set[set.length - 1][1], !includeLowers, !includeUppers); + }); + + c._ondirectionchange = function (direction) { + if (direction === "next") { + checkKey = keyIsBeyondCurrentEntry; + sortDirection = ascending; + } else { + checkKey = keyIsBeforeCurrentEntry; + sortDirection = descending; + } + set.sort(rangeSorter); + }; + + c._addAlgorithm(function (cursor, advance, resolve) { + var key = cursor.key; + while (checkKey(key)) { + // The cursor has passed beyond this key. Check next. + ++i; + if (i === set.length) { + // There is no next. Stop searching. + advance(resolve); + return false; + } + } + if (keyWithinCurrentRange(key)) { + // The current cursor value should be included and we should continue a single step in case next item has the same key or possibly our next key in set. + return true; + } else if (cmp(key, set[i][1]) === 0 || cmp(key, set[i][0]) === 0) { + // includeUpper or includeLower is false so keyWithinCurrentRange() returns false even though we are at range border. + // Continue to next key but don't include this one. + return false; + } else { + // cursor.key not yet at set[i]. Forward cursor to the next key to hunt for. + advance(function () { + if (sortDirection === ascending) cursor.continue(set[i][0]);else cursor.continue(set[i][1]); + }); + return false; + } + }); + return c; + }, + startsWithAnyOf: function () { + var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + + if (!set.every(function (s) { + return typeof s === 'string'; + })) { + return fail(this, "startsWithAnyOf() only works with strings"); + } + if (set.length === 0) return emptyCollection(this); + + return this.inAnyRange(set.map(function (str) { + return [str, str + maxString]; + })); + } + }; + }); + + // + // + // + // Collection Class + // + // + // + function Collection(whereClause, keyRangeGenerator) { + /// + /// + /// + /// Where clause instance + /// + var keyRange = null, + error = null; + if (keyRangeGenerator) try { + keyRange = keyRangeGenerator(); + } catch (ex) { + error = ex; + } + + var whereCtx = whereClause._ctx, + table = whereCtx.table; + this._ctx = { + table: table, + index: whereCtx.index, + isPrimKey: !whereCtx.index || table.schema.primKey.keyPath && whereCtx.index === table.schema.primKey.name, + range: keyRange, + keysOnly: false, + dir: "next", + unique: "", + algorithm: null, + filter: null, + replayFilter: null, + justLimit: true, // True if a replayFilter is just a filter that performs a "limit" operation (or none at all) + isMatch: null, + offset: 0, + limit: Infinity, + error: error, // If set, any promise must be rejected with this error + or: whereCtx.or, + valueMapper: table.hook.reading.fire + }; + } + + function isPlainKeyRange(ctx, ignoreLimitFilter) { + return !(ctx.filter || ctx.algorithm || ctx.or) && (ignoreLimitFilter ? ctx.justLimit : !ctx.replayFilter); + } + + props(Collection.prototype, function () { + + // + // Collection Private Functions + // + + function addFilter(ctx, fn) { + ctx.filter = combine(ctx.filter, fn); + } + + function addReplayFilter(ctx, factory, isLimitFilter) { + var curr = ctx.replayFilter; + ctx.replayFilter = curr ? function () { + return combine(curr(), factory()); + } : factory; + ctx.justLimit = isLimitFilter && !curr; + } + + function addMatchFilter(ctx, fn) { + ctx.isMatch = combine(ctx.isMatch, fn); + } + + /** @param ctx { + * isPrimKey: boolean, + * table: Table, + * index: string + * } + * @param store IDBObjectStore + **/ + function getIndexOrStore(ctx, store) { + if (ctx.isPrimKey) return store; + var indexSpec = ctx.table.schema.idxByName[ctx.index]; + if (!indexSpec) throw new exceptions.Schema("KeyPath " + ctx.index + " on object store " + store.name + " is not indexed"); + return store.index(indexSpec.name); + } + + /** @param ctx { + * isPrimKey: boolean, + * table: Table, + * index: string, + * keysOnly: boolean, + * range?: IDBKeyRange, + * dir: "next" | "prev" + * } + */ + function openCursor(ctx, store) { + var idxOrStore = getIndexOrStore(ctx, store); + return ctx.keysOnly && 'openKeyCursor' in idxOrStore ? idxOrStore.openKeyCursor(ctx.range || null, ctx.dir + ctx.unique) : idxOrStore.openCursor(ctx.range || null, ctx.dir + ctx.unique); + } + + function iter(ctx, fn, resolve, reject, idbstore) { + var filter = ctx.replayFilter ? combine(ctx.filter, ctx.replayFilter()) : ctx.filter; + if (!ctx.or) { + iterate(openCursor(ctx, idbstore), combine(ctx.algorithm, filter), fn, resolve, reject, !ctx.keysOnly && ctx.valueMapper); + } else (function () { + var set = {}; + var resolved = 0; + + function resolveboth() { + if (++resolved === 2) resolve(); // Seems like we just support or btwn max 2 expressions, but there are no limit because we do recursion. + } + + function union(item, cursor, advance) { + if (!filter || filter(cursor, advance, resolveboth, reject)) { + var key = cursor.primaryKey.toString(); // Converts any Date to String, String to String, Number to String and Array to comma-separated string + if (!hasOwn(set, key)) { + set[key] = true; + fn(item, cursor, advance); + } + } + } + + ctx.or._iterate(union, resolveboth, reject, idbstore); + iterate(openCursor(ctx, idbstore), ctx.algorithm, union, resolveboth, reject, !ctx.keysOnly && ctx.valueMapper); + })(); + } + function getInstanceTemplate(ctx) { + return ctx.table.schema.instanceTemplate; + } + + return { + + // + // Collection Protected Functions + // + + _read: function (fn, cb) { + var ctx = this._ctx; + return ctx.error ? ctx.table._trans(null, rejection.bind(null, ctx.error)) : ctx.table._idbstore(READONLY, fn).then(cb); + }, + _write: function (fn) { + var ctx = this._ctx; + return ctx.error ? ctx.table._trans(null, rejection.bind(null, ctx.error)) : ctx.table._idbstore(READWRITE, fn, "locked"); // When doing write operations on collections, always lock the operation so that upcoming operations gets queued. + }, + _addAlgorithm: function (fn) { + var ctx = this._ctx; + ctx.algorithm = combine(ctx.algorithm, fn); + }, + + _iterate: function (fn, resolve, reject, idbstore) { + return iter(this._ctx, fn, resolve, reject, idbstore); + }, + + clone: function (props$$1) { + var rv = Object.create(this.constructor.prototype), + ctx = Object.create(this._ctx); + if (props$$1) extend(ctx, props$$1); + rv._ctx = ctx; + return rv; + }, + + raw: function () { + this._ctx.valueMapper = null; + return this; + }, + + // + // Collection Public methods + // + + each: function (fn) { + var ctx = this._ctx; + + if (fake) { + var item = getInstanceTemplate(ctx), + primKeyPath = ctx.table.schema.primKey.keyPath, + key = getByKeyPath(item, ctx.index ? ctx.table.schema.idxByName[ctx.index].keyPath : primKeyPath), + primaryKey = getByKeyPath(item, primKeyPath); + fn(item, { key: key, primaryKey: primaryKey }); + } + + return this._read(function (resolve, reject, idbstore) { + iter(ctx, fn, resolve, reject, idbstore); + }); + }, + + count: function (cb) { + if (fake) return Promise.resolve(0).then(cb); + var ctx = this._ctx; + + if (isPlainKeyRange(ctx, true)) { + // This is a plain key range. We can use the count() method if the index. + return this._read(function (resolve, reject, idbstore) { + var idx = getIndexOrStore(ctx, idbstore); + var req = ctx.range ? idx.count(ctx.range) : idx.count(); + req.onerror = eventRejectHandler(reject); + req.onsuccess = function (e) { + resolve(Math.min(e.target.result, ctx.limit)); + }; + }, cb); + } else { + // Algorithms, filters or expressions are applied. Need to count manually. + var count = 0; + return this._read(function (resolve, reject, idbstore) { + iter(ctx, function () { + ++count;return false; + }, function () { + resolve(count); + }, reject, idbstore); + }, cb); + } + }, + + sortBy: function (keyPath, cb) { + /// + var parts = keyPath.split('.').reverse(), + lastPart = parts[0], + lastIndex = parts.length - 1; + function getval(obj, i) { + if (i) return getval(obj[parts[i]], i - 1); + return obj[lastPart]; + } + var order = this._ctx.dir === "next" ? 1 : -1; + + function sorter(a, b) { + var aVal = getval(a, lastIndex), + bVal = getval(b, lastIndex); + return aVal < bVal ? -order : aVal > bVal ? order : 0; + } + return this.toArray(function (a) { + return a.sort(sorter); + }).then(cb); + }, + + toArray: function (cb) { + var ctx = this._ctx; + return this._read(function (resolve, reject, idbstore) { + fake && resolve([getInstanceTemplate(ctx)]); + if (hasGetAll && ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) { + // Special optimation if we could use IDBObjectStore.getAll() or + // IDBKeyRange.getAll(): + var readingHook = ctx.table.hook.reading.fire; + var idxOrStore = getIndexOrStore(ctx, idbstore); + var req = ctx.limit < Infinity ? idxOrStore.getAll(ctx.range, ctx.limit) : idxOrStore.getAll(ctx.range); + req.onerror = eventRejectHandler(reject); + req.onsuccess = readingHook === mirror ? eventSuccessHandler(resolve) : eventSuccessHandler(function (res) { + try { + resolve(res.map(readingHook)); + } catch (e) { + reject(e); + } + }); + } else { + // Getting array through a cursor. + var a = []; + iter(ctx, function (item) { + a.push(item); + }, function arrayComplete() { + resolve(a); + }, reject, idbstore); + } + }, cb); + }, + + offset: function (offset) { + var ctx = this._ctx; + if (offset <= 0) return this; + ctx.offset += offset; // For count() + if (isPlainKeyRange(ctx)) { + addReplayFilter(ctx, function () { + var offsetLeft = offset; + return function (cursor, advance) { + if (offsetLeft === 0) return true; + if (offsetLeft === 1) { + --offsetLeft;return false; + } + advance(function () { + cursor.advance(offsetLeft); + offsetLeft = 0; + }); + return false; + }; + }); + } else { + addReplayFilter(ctx, function () { + var offsetLeft = offset; + return function () { + return --offsetLeft < 0; + }; + }); + } + return this; + }, + + limit: function (numRows) { + this._ctx.limit = Math.min(this._ctx.limit, numRows); // For count() + addReplayFilter(this._ctx, function () { + var rowsLeft = numRows; + return function (cursor, advance, resolve) { + if (--rowsLeft <= 0) advance(resolve); // Stop after this item has been included + return rowsLeft >= 0; // If numRows is already below 0, return false because then 0 was passed to numRows initially. Otherwise we wouldnt come here. + }; + }, true); + return this; + }, + + until: function (filterFunction, bIncludeStopEntry) { + var ctx = this._ctx; + fake && filterFunction(getInstanceTemplate(ctx)); + addFilter(this._ctx, function (cursor, advance, resolve) { + if (filterFunction(cursor.value)) { + advance(resolve); + return bIncludeStopEntry; + } else { + return true; + } + }); + return this; + }, + + first: function (cb) { + return this.limit(1).toArray(function (a) { + return a[0]; + }).then(cb); + }, + + last: function (cb) { + return this.reverse().first(cb); + }, + + filter: function (filterFunction) { + /// function(val){return true/false} + fake && filterFunction(getInstanceTemplate(this._ctx)); + addFilter(this._ctx, function (cursor) { + return filterFunction(cursor.value); + }); + // match filters not used in Dexie.js but can be used by 3rd part libraries to test a + // collection for a match without querying DB. Used by Dexie.Observable. + addMatchFilter(this._ctx, filterFunction); + return this; + }, + + and: function (filterFunction) { + return this.filter(filterFunction); + }, + + or: function (indexName) { + return new WhereClause(this._ctx.table, indexName, this); + }, + + reverse: function () { + this._ctx.dir = this._ctx.dir === "prev" ? "next" : "prev"; + if (this._ondirectionchange) this._ondirectionchange(this._ctx.dir); + return this; + }, + + desc: function () { + return this.reverse(); + }, + + eachKey: function (cb) { + var ctx = this._ctx; + ctx.keysOnly = !ctx.isMatch; + return this.each(function (val, cursor) { + cb(cursor.key, cursor); + }); + }, + + eachUniqueKey: function (cb) { + this._ctx.unique = "unique"; + return this.eachKey(cb); + }, + + eachPrimaryKey: function (cb) { + var ctx = this._ctx; + ctx.keysOnly = !ctx.isMatch; + return this.each(function (val, cursor) { + cb(cursor.primaryKey, cursor); + }); + }, + + keys: function (cb) { + var ctx = this._ctx; + ctx.keysOnly = !ctx.isMatch; + var a = []; + return this.each(function (item, cursor) { + a.push(cursor.key); + }).then(function () { + return a; + }).then(cb); + }, + + primaryKeys: function (cb) { + var ctx = this._ctx; + if (hasGetAll && ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) { + // Special optimation if we could use IDBObjectStore.getAllKeys() or + // IDBKeyRange.getAllKeys(): + return this._read(function (resolve, reject, idbstore) { + var idxOrStore = getIndexOrStore(ctx, idbstore); + var req = ctx.limit < Infinity ? idxOrStore.getAllKeys(ctx.range, ctx.limit) : idxOrStore.getAllKeys(ctx.range); + req.onerror = eventRejectHandler(reject); + req.onsuccess = eventSuccessHandler(resolve); + }).then(cb); + } + ctx.keysOnly = !ctx.isMatch; + var a = []; + return this.each(function (item, cursor) { + a.push(cursor.primaryKey); + }).then(function () { + return a; + }).then(cb); + }, + + uniqueKeys: function (cb) { + this._ctx.unique = "unique"; + return this.keys(cb); + }, + + firstKey: function (cb) { + return this.limit(1).keys(function (a) { + return a[0]; + }).then(cb); + }, + + lastKey: function (cb) { + return this.reverse().firstKey(cb); + }, + + distinct: function () { + var ctx = this._ctx, + idx = ctx.index && ctx.table.schema.idxByName[ctx.index]; + if (!idx || !idx.multi) return this; // distinct() only makes differencies on multiEntry indexes. + var set = {}; + addFilter(this._ctx, function (cursor) { + var strKey = cursor.primaryKey.toString(); // Converts any Date to String, String to String, Number to String and Array to comma-separated string + var found = hasOwn(set, strKey); + set[strKey] = true; + return !found; + }); + return this; + }, + + // + // Methods that mutate storage + // + + modify: function (changes) { + var self = this, + ctx = this._ctx, + hook = ctx.table.hook, + updatingHook = hook.updating.fire, + deletingHook = hook.deleting.fire; + + fake && typeof changes === 'function' && changes.call({ value: ctx.table.schema.instanceTemplate }, ctx.table.schema.instanceTemplate); + + return this._write(function (resolve, reject, idbstore, trans) { + var modifyer; + if (typeof changes === 'function') { + // Changes is a function that may update, add or delete propterties or even require a deletion the object itself (delete this.item) + if (updatingHook === nop && deletingHook === nop) { + // Noone cares about what is being changed. Just let the modifier function be the given argument as is. + modifyer = changes; + } else { + // People want to know exactly what is being modified or deleted. + // Let modifyer be a proxy function that finds out what changes the caller is actually doing + // and call the hooks accordingly! + modifyer = function (item) { + var origItem = deepClone(item); // Clone the item first so we can compare laters. + if (changes.call(this, item, this) === false) return false; // Call the real modifyer function (If it returns false explicitely, it means it dont want to modify anyting on this object) + if (!hasOwn(this, "value")) { + // The real modifyer function requests a deletion of the object. Inform the deletingHook that a deletion is taking place. + deletingHook.call(this, this.primKey, item, trans); + } else { + // No deletion. Check what was changed + var objectDiff = getObjectDiff(origItem, this.value); + var additionalChanges = updatingHook.call(this, objectDiff, this.primKey, origItem, trans); + if (additionalChanges) { + // Hook want to apply additional modifications. Make sure to fullfill the will of the hook. + item = this.value; + keys(additionalChanges).forEach(function (keyPath) { + setByKeyPath(item, keyPath, additionalChanges[keyPath]); // Adding {keyPath: undefined} means that the keyPath should be deleted. Handled by setByKeyPath + }); + } + } + }; + } + } else if (updatingHook === nop) { + // changes is a set of {keyPath: value} and no one is listening to the updating hook. + var keyPaths = keys(changes); + var numKeys = keyPaths.length; + modifyer = function (item) { + var anythingModified = false; + for (var i = 0; i < numKeys; ++i) { + var keyPath = keyPaths[i], + val = changes[keyPath]; + if (getByKeyPath(item, keyPath) !== val) { + setByKeyPath(item, keyPath, val); // Adding {keyPath: undefined} means that the keyPath should be deleted. Handled by setByKeyPath + anythingModified = true; + } + } + return anythingModified; + }; + } else { + // changes is a set of {keyPath: value} and people are listening to the updating hook so we need to call it and + // allow it to add additional modifications to make. + var origChanges = changes; + changes = shallowClone(origChanges); // Let's work with a clone of the changes keyPath/value set so that we can restore it in case a hook extends it. + modifyer = function (item) { + var anythingModified = false; + var additionalChanges = updatingHook.call(this, changes, this.primKey, deepClone(item), trans); + if (additionalChanges) extend(changes, additionalChanges); + keys(changes).forEach(function (keyPath) { + var val = changes[keyPath]; + if (getByKeyPath(item, keyPath) !== val) { + setByKeyPath(item, keyPath, val); + anythingModified = true; + } + }); + if (additionalChanges) changes = shallowClone(origChanges); // Restore original changes for next iteration + return anythingModified; + }; + } + + var count = 0; + var successCount = 0; + var iterationComplete = false; + var failures = []; + var failKeys = []; + var currentKey = null; + + function modifyItem(item, cursor) { + currentKey = cursor.primaryKey; + var thisContext = { + primKey: cursor.primaryKey, + value: item, + onsuccess: null, + onerror: null + }; + + function onerror(e) { + failures.push(e); + failKeys.push(thisContext.primKey); + checkFinished(); + return true; // Catch these errors and let a final rejection decide whether or not to abort entire transaction + } + + if (modifyer.call(thisContext, item, thisContext) !== false) { + // If a callback explicitely returns false, do not perform the update! + var bDelete = !hasOwn(thisContext, "value"); + ++count; + tryCatch(function () { + var req = bDelete ? cursor.delete() : cursor.update(thisContext.value); + req._hookCtx = thisContext; + req.onerror = hookedEventRejectHandler(onerror); + req.onsuccess = hookedEventSuccessHandler(function () { + ++successCount; + checkFinished(); + }); + }, onerror); + } else if (thisContext.onsuccess) { + // Hook will expect either onerror or onsuccess to always be called! + thisContext.onsuccess(thisContext.value); + } + } + + function doReject(e) { + if (e) { + failures.push(e); + failKeys.push(currentKey); + } + return reject(new ModifyError("Error modifying one or more objects", failures, successCount, failKeys)); + } + + function checkFinished() { + if (iterationComplete && successCount + failures.length === count) { + if (failures.length > 0) doReject();else resolve(successCount); + } + } + self.clone().raw()._iterate(modifyItem, function () { + iterationComplete = true; + checkFinished(); + }, doReject, idbstore); + }); + }, + + 'delete': function () { + var _this6 = this; + + var ctx = this._ctx, + range = ctx.range, + deletingHook = ctx.table.hook.deleting.fire, + hasDeleteHook = deletingHook !== nop; + if (!hasDeleteHook && isPlainKeyRange(ctx) && (ctx.isPrimKey && !hangsOnDeleteLargeKeyRange || !range)) // if no range, we'll use clear(). + { + // May use IDBObjectStore.delete(IDBKeyRange) in this case (Issue #208) + // For chromium, this is the way most optimized version. + // For IE/Edge, this could hang the indexedDB engine and make operating system instable + // (https://gist.github.com/dfahlander/5a39328f029de18222cf2125d56c38f7) + return this._write(function (resolve, reject, idbstore) { + // Our API contract is to return a count of deleted items, so we have to count() before delete(). + var onerror = eventRejectHandler(reject), + countReq = range ? idbstore.count(range) : idbstore.count(); + countReq.onerror = onerror; + countReq.onsuccess = function () { + var count = countReq.result; + tryCatch(function () { + var delReq = range ? idbstore.delete(range) : idbstore.clear(); + delReq.onerror = onerror; + delReq.onsuccess = function () { + return resolve(count); + }; + }, function (err) { + return reject(err); + }); + }; + }); + } + + // Default version to use when collection is not a vanilla IDBKeyRange on the primary key. + // Divide into chunks to not starve RAM. + // If has delete hook, we will have to collect not just keys but also objects, so it will use + // more memory and need lower chunk size. + var CHUNKSIZE = hasDeleteHook ? 2000 : 10000; + + return this._write(function (resolve, reject, idbstore, trans) { + var totalCount = 0; + // Clone collection and change its table and set a limit of CHUNKSIZE on the cloned Collection instance. + var collection = _this6.clone({ + keysOnly: !ctx.isMatch && !hasDeleteHook }) // load just keys (unless filter() or and() or deleteHook has subscribers) + .distinct() // In case multiEntry is used, never delete same key twice because resulting count + // would become larger than actual delete count. + .limit(CHUNKSIZE).raw(); // Don't filter through reading-hooks (like mapped classes etc) + + var keysOrTuples = []; + + // We're gonna do things on as many chunks that are needed. + // Use recursion of nextChunk function: + var nextChunk = function () { + return collection.each(hasDeleteHook ? function (val, cursor) { + // Somebody subscribes to hook('deleting'). Collect all primary keys and their values, + // so that the hook can be called with its values in bulkDelete(). + keysOrTuples.push([cursor.primaryKey, cursor.value]); + } : function (val, cursor) { + // No one subscribes to hook('deleting'). Collect only primary keys: + keysOrTuples.push(cursor.primaryKey); + }).then(function () { + // Chromium deletes faster when doing it in sort order. + hasDeleteHook ? keysOrTuples.sort(function (a, b) { + return ascending(a[0], b[0]); + }) : keysOrTuples.sort(ascending); + return bulkDelete(idbstore, trans, keysOrTuples, hasDeleteHook, deletingHook); + }).then(function () { + var count = keysOrTuples.length; + totalCount += count; + keysOrTuples = []; + return count < CHUNKSIZE ? totalCount : nextChunk(); + }); + }; + + resolve(nextChunk()); + }); + } + }; + }); + + // + // + // + // ------------------------- Help functions --------------------------- + // + // + // + + function lowerVersionFirst(a, b) { + return a._cfg.version - b._cfg.version; + } + + function setApiOnPlace(objs, tableNames, dbschema) { + tableNames.forEach(function (tableName) { + var schema = dbschema[tableName]; + objs.forEach(function (obj) { + if (!(tableName in obj)) { + if (obj === Transaction.prototype || obj instanceof Transaction) { + // obj is a Transaction prototype (or prototype of a subclass to Transaction) + // Make the API a getter that returns this.table(tableName) + setProp(obj, tableName, { + get: function () { + return this.table(tableName); + } + }); + } else { + // Table will not be bound to a transaction (will use Dexie.currentTransaction) + obj[tableName] = new Table(tableName, schema); + } + } + }); + }); + } + + function removeTablesApi(objs) { + objs.forEach(function (obj) { + for (var key in obj) { + if (obj[key] instanceof Table) delete obj[key]; + } + }); + } + + function iterate(req, filter, fn, resolve, reject, valueMapper) { + + // Apply valueMapper (hook('reading') or mappped class) + var mappedFn = valueMapper ? function (x, c, a) { + return fn(valueMapper(x), c, a); + } : fn; + // Wrap fn with PSD and microtick stuff from Promise. + var wrappedFn = wrap(mappedFn, reject); + + if (!req.onerror) req.onerror = eventRejectHandler(reject); + if (filter) { + req.onsuccess = trycatcher(function filter_record() { + var cursor = req.result; + if (cursor) { + var c = function () { + cursor.continue(); + }; + if (filter(cursor, function (advancer) { + c = advancer; + }, resolve, reject)) wrappedFn(cursor.value, cursor, function (advancer) { + c = advancer; + }); + c(); + } else { + resolve(); + } + }, reject); + } else { + req.onsuccess = trycatcher(function filter_record() { + var cursor = req.result; + if (cursor) { + var c = function () { + cursor.continue(); + }; + wrappedFn(cursor.value, cursor, function (advancer) { + c = advancer; + }); + c(); + } else { + resolve(); + } + }, reject); + } + } + + function parseIndexSyntax(indexes) { + /// + /// + var rv = []; + indexes.split(',').forEach(function (index) { + index = index.trim(); + var name = index.replace(/([&*]|\+\+)/g, ""); // Remove "&", "++" and "*" + // Let keyPath of "[a+b]" be ["a","b"]: + var keyPath = /^\[/.test(name) ? name.match(/^\[(.*)\]$/)[1].split('+') : name; + + rv.push(new IndexSpec(name, keyPath || null, /\&/.test(index), /\*/.test(index), /\+\+/.test(index), isArray(keyPath), /\./.test(index))); + }); + return rv; + } + + function cmp(key1, key2) { + return indexedDB.cmp(key1, key2); + } + + function min(a, b) { + return cmp(a, b) < 0 ? a : b; + } + + function max(a, b) { + return cmp(a, b) > 0 ? a : b; + } + + function ascending(a, b) { + return indexedDB.cmp(a, b); + } + + function descending(a, b) { + return indexedDB.cmp(b, a); + } + + function simpleCompare(a, b) { + return a < b ? -1 : a === b ? 0 : 1; + } + + function simpleCompareReverse(a, b) { + return a > b ? -1 : a === b ? 0 : 1; + } + + function combine(filter1, filter2) { + return filter1 ? filter2 ? function () { + return filter1.apply(this, arguments) && filter2.apply(this, arguments); + } : filter1 : filter2; + } + + function readGlobalSchema() { + db.verno = idbdb.version / 10; + db._dbSchema = globalSchema = {}; + dbStoreNames = slice(idbdb.objectStoreNames, 0); + if (dbStoreNames.length === 0) return; // Database contains no stores. + var trans = idbdb.transaction(safariMultiStoreFix(dbStoreNames), 'readonly'); + dbStoreNames.forEach(function (storeName) { + var store = trans.objectStore(storeName), + keyPath = store.keyPath, + dotted = keyPath && typeof keyPath === 'string' && keyPath.indexOf('.') !== -1; + var primKey = new IndexSpec(keyPath, keyPath || "", false, false, !!store.autoIncrement, keyPath && typeof keyPath !== 'string', dotted); + var indexes = []; + for (var j = 0; j < store.indexNames.length; ++j) { + var idbindex = store.index(store.indexNames[j]); + keyPath = idbindex.keyPath; + dotted = keyPath && typeof keyPath === 'string' && keyPath.indexOf('.') !== -1; + var index = new IndexSpec(idbindex.name, keyPath, !!idbindex.unique, !!idbindex.multiEntry, false, keyPath && typeof keyPath !== 'string', dotted); + indexes.push(index); + } + globalSchema[storeName] = new TableSchema(storeName, primKey, indexes, {}); + }); + setApiOnPlace([allTables], keys(globalSchema), globalSchema); + } + + function adjustToExistingIndexNames(schema, idbtrans) { + /// + /// Issue #30 Problem with existing db - adjust to existing index names when migrating from non-dexie db + /// + /// Map between name and TableSchema + /// + var storeNames = idbtrans.db.objectStoreNames; + for (var i = 0; i < storeNames.length; ++i) { + var storeName = storeNames[i]; + var store = idbtrans.objectStore(storeName); + hasGetAll = 'getAll' in store; + for (var j = 0; j < store.indexNames.length; ++j) { + var indexName = store.indexNames[j]; + var keyPath = store.index(indexName).keyPath; + var dexieName = typeof keyPath === 'string' ? keyPath : "[" + slice(keyPath).join('+') + "]"; + if (schema[storeName]) { + var indexSpec = schema[storeName].idxByName[dexieName]; + if (indexSpec) indexSpec.name = indexName; + } + } + } + } + + function fireOnBlocked(ev) { + db.on("blocked").fire(ev); + // Workaround (not fully*) for missing "versionchange" event in IE,Edge and Safari: + connections.filter(function (c) { + return c.name === db.name && c !== db && !c._vcFired; + }).map(function (c) { + return c.on("versionchange").fire(ev); + }); + } + + extend(this, { + Collection: Collection, + Table: Table, + Transaction: Transaction, + Version: Version, + WhereClause: WhereClause + }); + + init(); + + addons.forEach(function (fn) { + fn(db); + }); +} + +var fakeAutoComplete = function () {}; // Will never be changed. We just fake for the IDE that we change it (see doFakeAutoComplete()) +var fake = false; // Will never be changed. We just fake for the IDE that we change it (see doFakeAutoComplete()) + +function parseType(type) { + if (typeof type === 'function') { + return new type(); + } else if (isArray(type)) { + return [parseType(type[0])]; + } else if (type && typeof type === 'object') { + var rv = {}; + applyStructure(rv, type); + return rv; + } else { + return type; + } +} + +function applyStructure(obj, structure) { + keys(structure).forEach(function (member) { + var value = parseType(structure[member]); + obj[member] = value; + }); + return obj; +} + +function hookedEventSuccessHandler(resolve) { + // wrap() is needed when calling hooks because the rare scenario of: + // * hook does a db operation that fails immediately (IDB throws exception) + // For calling db operations on correct transaction, wrap makes sure to set PSD correctly. + // wrap() will also execute in a virtual tick. + // * If not wrapped in a virtual tick, direct exception will launch a new physical tick. + // * If this was the last event in the bulk, the promise will resolve after a physical tick + // and the transaction will have committed already. + // If no hook, the virtual tick will be executed in the reject()/resolve of the final promise, + // because it is always marked with _lib = true when created using Transaction._promise(). + return wrap(function (event) { + var req = event.target, + result = req.result, + ctx = req._hookCtx, + // Contains the hook error handler. Put here instead of closure to boost performance. + hookSuccessHandler = ctx && ctx.onsuccess; + hookSuccessHandler && hookSuccessHandler(result); + resolve && resolve(result); + }, resolve); +} + +function eventRejectHandler(reject) { + return wrap(function (event) { + preventDefault(event); + reject(event.target.error); + return false; + }); +} + +function eventSuccessHandler(resolve) { + return wrap(function (event) { + resolve(event.target.result); + }); +} + +function hookedEventRejectHandler(reject) { + return wrap(function (event) { + // See comment on hookedEventSuccessHandler() why wrap() is needed only when supporting hooks. + + var req = event.target, + err = req.error, + ctx = req._hookCtx, + // Contains the hook error handler. Put here instead of closure to boost performance. + hookErrorHandler = ctx && ctx.onerror; + hookErrorHandler && hookErrorHandler(err); + preventDefault(event); + reject(err); + return false; + }); +} + +function preventDefault(event) { + if (event.stopPropagation) // IndexedDBShim doesnt support this on Safari 8 and below. + event.stopPropagation(); + if (event.preventDefault) // IndexedDBShim doesnt support this on Safari 8 and below. + event.preventDefault(); +} + +function globalDatabaseList(cb) { + var val, + localStorage = Dexie.dependencies.localStorage; + if (!localStorage) return cb([]); // Envs without localStorage support + try { + val = JSON.parse(localStorage.getItem('Dexie.DatabaseNames') || "[]"); + } catch (e) { + val = []; + } + if (cb(val)) { + localStorage.setItem('Dexie.DatabaseNames', JSON.stringify(val)); + } +} + +function awaitIterator(iterator) { + var callNext = function (result) { + return iterator.next(result); + }, + doThrow = function (error) { + return iterator.throw(error); + }, + onSuccess = step(callNext), + onError = step(doThrow); + + function step(getNext) { + return function (val) { + var next = getNext(val), + value = next.value; + + return next.done ? value : !value || typeof value.then !== 'function' ? isArray(value) ? Promise.all(value).then(onSuccess, onError) : onSuccess(value) : value.then(onSuccess, onError); + }; + } + + return step(callNext)(); +} + +// +// IndexSpec struct +// +function IndexSpec(name, keyPath, unique, multi, auto, compound, dotted) { + /// + /// + /// + /// + /// + /// + /// + this.name = name; + this.keyPath = keyPath; + this.unique = unique; + this.multi = multi; + this.auto = auto; + this.compound = compound; + this.dotted = dotted; + var keyPathSrc = typeof keyPath === 'string' ? keyPath : keyPath && '[' + [].join.call(keyPath, '+') + ']'; + this.src = (unique ? '&' : '') + (multi ? '*' : '') + (auto ? "++" : "") + keyPathSrc; +} + +// +// TableSchema struct +// +function TableSchema(name, primKey, indexes, instanceTemplate) { + /// + /// + /// + /// + this.name = name; + this.primKey = primKey || new IndexSpec(); + this.indexes = indexes || [new IndexSpec()]; + this.instanceTemplate = instanceTemplate; + this.mappedClass = null; + this.idxByName = arrayToObject(indexes, function (index) { + return [index.name, index]; + }); +} + +// Used in when defining dependencies later... +// (If IndexedDBShim is loaded, prefer it before standard indexedDB) +var idbshim = _global.idbModules && _global.idbModules.shimIndexedDB ? _global.idbModules : {}; + +function safariMultiStoreFix(storeNames) { + return storeNames.length === 1 ? storeNames[0] : storeNames; +} + +function getNativeGetDatabaseNamesFn(indexedDB) { + var fn = indexedDB && (indexedDB.getDatabaseNames || indexedDB.webkitGetDatabaseNames); + return fn && fn.bind(indexedDB); +} + +// Export Error classes +props(Dexie, fullNameExceptions); // Dexie.XXXError = class XXXError {...}; + +// +// Static methods and properties +// +props(Dexie, { + + // + // Static delete() method. + // + delete: function (databaseName) { + var db = new Dexie(databaseName), + promise = db.delete(); + promise.onblocked = function (fn) { + db.on("blocked", fn); + return this; + }; + return promise; + }, + + // + // Static exists() method. + // + exists: function (name) { + return new Dexie(name).open().then(function (db) { + db.close(); + return true; + }).catch(Dexie.NoSuchDatabaseError, function () { + return false; + }); + }, + + // + // Static method for retrieving a list of all existing databases at current host. + // + getDatabaseNames: function (cb) { + return new Promise(function (resolve, reject) { + var getDatabaseNames = getNativeGetDatabaseNamesFn(indexedDB); + if (getDatabaseNames) { + // In case getDatabaseNames() becomes standard, let's prepare to support it: + var req = getDatabaseNames(); + req.onsuccess = function (event) { + resolve(slice(event.target.result, 0)); // Converst DOMStringList to Array + }; + req.onerror = eventRejectHandler(reject); + } else { + globalDatabaseList(function (val) { + resolve(val); + return false; + }); + } + }).then(cb); + }, + + defineClass: function (structure) { + /// + /// Create a javascript constructor based on given template for which properties to expect in the class. + /// Any property that is a constructor function will act as a type. So {name: String} will be equal to {name: new String()}. + /// + /// Helps IDE code completion by knowing the members that objects contain and not just the indexes. Also + /// know what type each member has. Example: {name: String, emailAddresses: [String], properties: {shoeSize: Number}} + + // Default constructor able to copy given properties into this object. + function Class(properties) { + /// Properties to initialize object with. + /// + properties ? extend(this, properties) : fake && applyStructure(this, structure); + } + return Class; + }, + + applyStructure: applyStructure, + + ignoreTransaction: function (scopeFunc) { + // In case caller is within a transaction but needs to create a separate transaction. + // Example of usage: + // + // Let's say we have a logger function in our app. Other application-logic should be unaware of the + // logger function and not need to include the 'logentries' table in all transaction it performs. + // The logging should always be done in a separate transaction and not be dependant on the current + // running transaction context. Then you could use Dexie.ignoreTransaction() to run code that starts a new transaction. + // + // Dexie.ignoreTransaction(function() { + // db.logentries.add(newLogEntry); + // }); + // + // Unless using Dexie.ignoreTransaction(), the above example would try to reuse the current transaction + // in current Promise-scope. + // + // An alternative to Dexie.ignoreTransaction() would be setImmediate() or setTimeout(). The reason we still provide an + // API for this because + // 1) The intention of writing the statement could be unclear if using setImmediate() or setTimeout(). + // 2) setTimeout() would wait unnescessary until firing. This is however not the case with setImmediate(). + // 3) setImmediate() is not supported in the ES standard. + // 4) You might want to keep other PSD state that was set in a parent PSD, such as PSD.letThrough. + return PSD.trans ? usePSD(PSD.transless, scopeFunc) : // Use the closest parent that was non-transactional. + scopeFunc(); // No need to change scope because there is no ongoing transaction. + }, + + vip: function (fn) { + // To be used by subscribers to the on('ready') event. + // This will let caller through to access DB even when it is blocked while the db.ready() subscribers are firing. + // This would have worked automatically if we were certain that the Provider was using Dexie.Promise for all asyncronic operations. The promise PSD + // from the provider.connect() call would then be derived all the way to when provider would call localDatabase.applyChanges(). But since + // the provider more likely is using non-promise async APIs or other thenable implementations, we cannot assume that. + // Note that this method is only useful for on('ready') subscribers that is returning a Promise from the event. If not using vip() + // the database could deadlock since it wont open until the returned Promise is resolved, and any non-VIPed operation started by + // the caller will not resolve until database is opened. + return newScope(function () { + PSD.letThrough = true; // Make sure we are let through if still blocking db due to onready is firing. + return fn(); + }); + }, + + async: function (generatorFn) { + return function () { + try { + var rv = awaitIterator(generatorFn.apply(this, arguments)); + if (!rv || typeof rv.then !== 'function') return Promise.resolve(rv); + return rv; + } catch (e) { + return rejection(e); + } + }; + }, + + spawn: function (generatorFn, args, thiz) { + try { + var rv = awaitIterator(generatorFn.apply(thiz, args || [])); + if (!rv || typeof rv.then !== 'function') return Promise.resolve(rv); + return rv; + } catch (e) { + return rejection(e); + } + }, + + // Dexie.currentTransaction property + currentTransaction: { + get: function () { + return PSD.trans || null; + } + }, + + waitFor: function (promiseOrFunction, optionalTimeout) { + // If a function is provided, invoke it and pass the returning value to Transaction.waitFor() + var promise = Promise.resolve(typeof promiseOrFunction === 'function' ? Dexie.ignoreTransaction(promiseOrFunction) : promiseOrFunction).timeout(optionalTimeout || 60000); // Default the timeout to one minute. Caller may specify Infinity if required. + + // Run given promise on current transaction. If no current transaction, just return a Dexie promise based + // on given value. + return PSD.trans ? PSD.trans.waitFor(promise) : promise; + }, + + // Export our Promise implementation since it can be handy as a standalone Promise implementation + Promise: Promise, + + // Dexie.debug proptery: + // Dexie.debug = false + // Dexie.debug = true + // Dexie.debug = "dexie" - don't hide dexie's stack frames. + debug: { + get: function () { + return debug; + }, + set: function (value) { + setDebug(value, value === 'dexie' ? function () { + return true; + } : dexieStackFrameFilter); + } + }, + + // Export our derive/extend/override methodology + derive: derive, + extend: extend, + props: props, + override: override, + // Export our Events() function - can be handy as a toolkit + Events: Events, + // Utilities + getByKeyPath: getByKeyPath, + setByKeyPath: setByKeyPath, + delByKeyPath: delByKeyPath, + shallowClone: shallowClone, + deepClone: deepClone, + getObjectDiff: getObjectDiff, + asap: asap, + maxKey: maxKey, + minKey: minKey, + // Addon registry + addons: [], + // Global DB connection list + connections: connections, + + MultiModifyError: exceptions.Modify, // Backward compatibility 0.9.8. Deprecate. + errnames: errnames, + + // Export other static classes + IndexSpec: IndexSpec, + TableSchema: TableSchema, + + // + // Dependencies + // + // These will automatically work in browsers with indexedDB support, or where an indexedDB polyfill has been included. + // + // In node.js, however, these properties must be set "manually" before instansiating a new Dexie(). + // For node.js, you need to require indexeddb-js or similar and then set these deps. + // + dependencies: { + // Required: + indexedDB: idbshim.shimIndexedDB || _global.indexedDB || _global.mozIndexedDB || _global.webkitIndexedDB || _global.msIndexedDB, + IDBKeyRange: idbshim.IDBKeyRange || _global.IDBKeyRange || _global.webkitIDBKeyRange + }, + + // API Version Number: Type Number, make sure to always set a version number that can be comparable correctly. Example: 0.9, 0.91, 0.92, 1.0, 1.01, 1.1, 1.2, 1.21, etc. + semVer: DEXIE_VERSION, + version: DEXIE_VERSION.split('.').map(function (n) { + return parseInt(n); + }).reduce(function (p, c, i) { + return p + c / Math.pow(10, i * 2); + }), + fakeAutoComplete: fakeAutoComplete, + + // https://github.com/dfahlander/Dexie.js/issues/186 + // typescript compiler tsc in mode ts-->es5 & commonJS, will expect require() to return + // x.default. Workaround: Set Dexie.default = Dexie. + default: Dexie +}); + +tryCatch(function () { + // Optional dependencies + // localStorage + Dexie.dependencies.localStorage = (typeof chrome !== "undefined" && chrome !== null ? chrome.storage : void 0) != null ? null : _global.localStorage; +}); + +// Map DOMErrors and DOMExceptions to corresponding Dexie errors. May change in Dexie v2.0. +Promise.rejectionMapper = mapError; + +// Fool IDE to improve autocomplete. Tested with Visual Studio 2013 and 2015. +doFakeAutoComplete(function () { + Dexie.fakeAutoComplete = fakeAutoComplete = doFakeAutoComplete; + Dexie.fake = fake = true; +}); + +return Dexie; + +}))); +//# sourceMappingURL=dexie.js.map From 858b5b944748486387f4304c2202d16c37960b8c Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Sat, 10 Dec 2016 15:12:49 +0100 Subject: [PATCH 05/15] Bugfix .gitignore --- .gitignore | 2 +- src/contentscripts/shazamLocalStorage.js | 94 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/contentscripts/shazamLocalStorage.js diff --git a/.gitignore b/.gitignore index 1f37a06..76a8483 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,6 @@ node_modules/ popup/popup.css popup/popup.js background/background.js -contentscripts/ +/contentscripts/ dist/ build/ \ No newline at end of file diff --git a/src/contentscripts/shazamLocalStorage.js b/src/contentscripts/shazamLocalStorage.js new file mode 100644 index 0000000..854e129 --- /dev/null +++ b/src/contentscripts/shazamLocalStorage.js @@ -0,0 +1,94 @@ +function retrieveWindowVariables(variables) { + var ret = {}; + + var scriptContent = ''; + for (var i = 0; i < variables.length; i++) { + var currVariable = variables[i]; + scriptContent += "if (typeof " + currVariable + " !== 'undefined') document.body.setAttribute('tmp_" + currVariable + "', JSON.stringify(" + currVariable + "));\n" + } + + var script = document.createElement('script'); + script.id = 'tmpScript'; + script.appendChild(document.createTextNode(scriptContent)); + (document.body || document.head || document.documentElement).appendChild(script); + + for (var i = 0; i < variables.length; i++) { + var currVariable = variables[i]; + ret[currVariable] = JSON.parse(document.body.getAttribute('tmp_' + currVariable)); + document.body.removeAttribute('tmp_' + currVariable); + } + + document.getElementById('tmpScript').remove(); + + return ret; +} + +function getAndSendLocalStorage() { + var pageVars = retrieveWindowVariables(['localStorage']); + + if(pageVars && pageVars.localStorage && pageVars.localStorage.inid) { + console.log('inid found on local storage : '+ pageVars.localStorage.inid); + console.log('Sending to Shazify...'); + + // Send a message to Shazify background script with the page's localStorage + chrome.runtime.sendMessage({ shazamLocalStorage: pageVars.localStorage }, function(response) { + if(!response || ('isFine' in response && response.isFine === false)) { + console.log('inid seems not fine... maybe an old one. Please login again on Shazam.'); + observeForLoginAndGetLocalStorage(); + } else { + console.log('inid seems fine! Congrats! You are now logged in on Shazam.'); + } + }); + + return true; + } + + return false; +} + +var observerIsSet = false; + +function observeForLoginAndGetLocalStorage() { + if(observerIsSet) { + return; + } + + observerIsSet = true; + + console.log('Observing for changes on the Shazam webpage, waiting for login...'); + + // Observe for changes on #/myshazam element, and when a change is detected, try to get localStorage again + var target = document.getElementsByClassName('main'); + + if(!target || target.length === 0) { + console.log('DOM not ready yet, waiting 1s...'); + observerIsSet = false; + + setTimeout(function() { + observeForLoginAndGetLocalStorage(); + }, 1000); + + return; + } + + target = target[0]; + + // create an observer instance + var observer = new MutationObserver(function() { + console.log('Change detected on the Shazam page, maybe a successful login... will check for inid in local storage.'); + if(getAndSendLocalStorage()) { + observerIsSet = false; + observer.disconnect(); + } + }); + + // configuration of the observer: + var config = { childList: true }; + + // pass in the target node, as well as the observer options + observer.observe(target, config); +} + +if(!getAndSendLocalStorage()) { + observeForLoginAndGetLocalStorage(); +} \ No newline at end of file From 5a7643e24e67b27fcb8b5dcf177e84330791e3fc Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Sat, 10 Dec 2016 15:29:13 +0100 Subject: [PATCH 06/15] Add key to manifest for development. Small changes. --- manifest.json | 3 ++- src/background/ShazamService.js | 19 ++++++++++--------- src/background/SpotifyService.js | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/manifest.json b/manifest.json index 6c3d0e9..f199ca9 100755 --- a/manifest.json +++ b/manifest.json @@ -1,4 +1,5 @@ { + "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAopSL/q/2d97dHxCQ1w26B4EONSEo4CZZYFqu3MlJKA5ifSXkyoNSlkG6afDuNoXmiTWFCAawRgGwuO3yi0QvfeMJpz+M9nXnt8PQsVP10Mb7o3SHIihxIXRMFAJvggvHW4NCuDznJixpo2ixEeCL8LGkS0ZL93MgYtEOuaGPdcBTiAUya3Nr3mdkn+nTHldQvthassiDYZystDTuKwkf0vTZkDCyWC8bfI7xZTxQ6IFcS2WQgUm0ix5hgv+qyOQzOwh26R6BQmZTFj7YiSqhgZsZRebhMcn+hxd8kSegS1ZAcKv/oS63Ej5/IAB5NijrPXR3zivQZ6oIJjr+xMOU9wIDAQAB", "name": "Shazify", "version": "0.3.2", "manifest_version": 2, @@ -26,7 +27,7 @@ "http://www.shazam.com/*", "https://www.shazam.com/*", "https://api.spotify.com/*", - "https://accounts.spotify.com/api/*" + "https://accounts.spotify.com/*" ], "content_security_policy": "script-src 'self' https://ssl.google-analytics.com https://fonts.googleapis.com; object-src 'self'" } \ No newline at end of file diff --git a/src/background/ShazamService.js b/src/background/ShazamService.js index b8b9078..23b0dbc 100644 --- a/src/background/ShazamService.js +++ b/src/background/ShazamService.js @@ -24,23 +24,23 @@ }); function receiveMessage(request, sender, sendResponse) { - if(request.shazamLocalStorage && request.shazamLocalStorage.inid) { - Shazam.setAndCheckInid(request.shazamLocalStorage.inid, function(isFine) { - sendResponse({ isFine: isFine }); + if(request.shazamLocalStorage && request.shazamLocalStorage.inid) { + Shazam.setAndCheckInid(request.shazamLocalStorage.inid, function(isFine) { + sendResponse({ isFine: isFine }); - if(!isFine) { - Shazam.data.set({ 'inid': null }); + if(!isFine) { + Shazam.data.set({ 'inid': null }); Logger.info('[Shazam] "inid" returned is not fine...'); } else { Logger.info('[Shazam] "inid" returned is fine!'); chrome.runtime.onMessage.removeListener(receiveMessage); } }); - } + } - // Let us use "sendResponse" asynchronously - return true; - } + // Let us use "sendResponse" asynchronously + return true; + } chrome.runtime.onMessage.addListener(receiveMessage); }, @@ -69,6 +69,7 @@ loginStatus: function(callback) { Shazam.data.get('inid', function(items) { if(!items.inid) { + Logger.info('[Shazam] login status : np inid stored.'); return callback(false); } diff --git a/src/background/SpotifyService.js b/src/background/SpotifyService.js index c19b904..790f584 100644 --- a/src/background/SpotifyService.js +++ b/src/background/SpotifyService.js @@ -397,7 +397,7 @@ // Helpers to get URLs for API calls getUrl: { redirect: function() { - return 'https://'+ chrome.runtime.id +'.chromiumapp.org/spotify_cb'; + return chrome.identity.getRedirectURL() +'spotify_cb'; }, authorize: function() { From a241e6b3b0d39cc40fd58aa4ada8f01d5ab797f6 Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Sat, 10 Dec 2016 16:38:11 +0100 Subject: [PATCH 07/15] Handle new Shazam API for tags fetching. Match track name in lower case. Add ROADMAP for v0.4.0 to README. --- README.md | 9 ++- src/background/ShazamService.js | 110 ++++++++++++++++++++----------- src/background/SpotifyService.js | 2 +- 3 files changed, 79 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 1805533..02870a4 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,18 @@ grunt build grunt bundle ``` -## Roadmap for v0.2.5 +## Roadmap for v0.4.0 - Custom update scripts, handle versions like 0.2.10 - Add tracks in order on Spotify - Reorder existing tracks +- Add filter functionnality from tag status (found, not found) +- Rounded tag image +- Store tags and Spotify tracks found in IndexedDB (use Dexie.js) +- Remove tags from localStorage +- When select a different Spotify track for a tag, remove the old one from playlist (if not associated with another tag) and add the new one +- Create an update page to let the users know of the new functionnalities +- Add an edit button for tracks found, to le the user know he can change the track found ## Disclaimer diff --git a/src/background/ShazamService.js b/src/background/ShazamService.js index 23b0dbc..49d7e2a 100644 --- a/src/background/ShazamService.js +++ b/src/background/ShazamService.js @@ -93,28 +93,64 @@ Logger.error(textStatus); return callback(false); }); - }); + }); }, // Download tags history, parse it and return a tags array - getTags: function(lastUpdate, callback) { - Logger.info('[Shazam] Downloading tags history...'); - $.get('https://www.shazam.com/myshazam/download-history') - .done(function(data) { - if(data) { - Shazam.parseTags(lastUpdate, data, callback); - } else { - Logger.error('[Shazam] Cannot download Shazam history.'); - Logger.error('[Shazam] Data returned : "'+ data +'"'); - - return callback(new Error('Cannot download Shazam history.')); - } - }) - .fail(function(jqXHR, textStatus) { - Logger.info('[Shazam] Tags fetch error : '+textStatus+'.'); + getTags: function(lastUpdate, callback, token) { + token = token || null; + + Logger.info('[Shazam] Getting list of tags...'); + Shazam.data.get('inid', function(items) { + if(!items.inid) { + Logger.error('[Shazam] cannot get tags, seems not to be logged in.'); + return callback(new Error('Cannot get tags, seems not to be logged in.')); + } + + $.getJSON('https://www.shazam.com/discovery/v4/fr/CH/web/-/tag/'+ items.inid +'?limit=200'+ ((token) ? '&token='+ token : '')) + .done(function(data) { + if(data && data.tags) { + Shazam.parseTags(lastUpdate, data, function(error, tags) { + if(error) { + Logger.error('[Shazam] error parsing tags: '+ error +'.'); + return callback(error); + } + + if(!tags || !tags.length) { + return callback(null, []); + } + + var lastTagDate = tags[tags.length-1].date; + + // If Shazam API returned a token, it's that we still have tags we can fetch + if(data.token && lastTagDate >= lastUpdate) { + Logger.info('[Shazam] more tags to fetch, calling getTags again.'); + // Recursive call to get all tags since the last update + Shazam.getTags(lastUpdate, function(error, newTags) { + if(error) { + Logger.error('[Shazam] error getting tags: '+ error +'.'); + return callback(error); + } + + return callback(null, tags.concat(newTags)); + }, data.token); + } else { + return callback(null, tags); + } + }); + } else { + Logger.error('[Shazam] Cannot get tags from Shazam.'); + Logger.error('[Shazam] Data returned : "'+ data +'"'); + + return callback(new Error('Cannot get tags from Shazam.')); + } + }) + .fail(function(jqXHR, textStatus) { + Logger.info('[Shazam] Tags fetch error : '+textStatus+'.'); - return callback(new Error('Tags fetch error : '+textStatus)); - }); + return callback(new Error('Tags fetch error : '+textStatus)); + }); + }); }, // Parse tags from tags history @@ -123,31 +159,26 @@ var tags = []; var stopParsing = false; - var tagsEl = $(data).find('tr'); - Logger.info('[Shazam] Start parsing of '+ tagsEl.length +' elements...'); + Logger.info('[Shazam] Start parsing of '+ data.tags.length +' elements...'); - for(var i = 0; i < tagsEl.length && stopParsing === false; i++) { - if($('td', tagsEl[i]).length === 0) { - continue; - } + var length = data.tags.length; + for(var i = 0; i < length && stopParsing === false; i++) { + var tagDate = new Date(data.tags[i].timestamp); + var tagName = data.tags[i].track.heading.title; + var tagArtist = data.tags[i].track.heading.subtitle; - var date = new Date($('td.time', tagsEl[i]).text()); + if(tagDate > lastUpdate) { + if(tagName && tagArtist) { + var tag = { + shazamId: data.tags[i].tagid, + name: data.tags[i].track.heading.title, + artist: data.tags[i].track.heading.subtitle, + date: tagDate + }; - if(date > lastUpdate) { - var idMatch = (new RegExp('t([0-9]+)', 'g')).exec($('td:nth-child(1) a', tagsEl[i]).attr('href')); - if(!idMatch) { - continue; + tags.push(tag); } - - var tag = { - shazamId: idMatch[1], - name: $('td:nth-child(1) a', tagsEl[i]).text().trim(), - artist: $('td:nth-child(2)', tagsEl[i]).text().trim(), - date: date - }; - - tags.push(tag); } else { // Tag's date is lower than last update date = the following tags were already fetched in previous updates Logger.info('[Shazam] Stop parsing, we reached the last tag not already fetched.'); @@ -155,9 +186,8 @@ } } - callback(null, tags); + return callback(null, tags); } - }; window.s2s.Shazam = Shazam; diff --git a/src/background/SpotifyService.js b/src/background/SpotifyService.js index 790f584..660ae71 100644 --- a/src/background/SpotifyService.js +++ b/src/background/SpotifyService.js @@ -322,7 +322,7 @@ // Only mark as found if the track name is exactly the same // TODO: should check for artists too - if(track.name == trackName) { + if(track.name.toLowerCase() == trackName.toLowerCase()) { found = track; } } From c748ef48bb1052517d031bdbe95e77d80761e3f8 Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Sun, 11 Dec 2016 20:56:54 +0100 Subject: [PATCH 08/15] Add modal when login success on Shazam. Add filter functionnality for tracks. Add Simple Line Icons. Handle "Too Many Requests" from Spotify. --- README.md | 3 +- gruntfile.js | 5 +- popup/fonts/Simple-Line-Icons.eot | Bin 0 -> 54266 bytes popup/fonts/Simple-Line-Icons.svg | 200 ++++++ popup/fonts/Simple-Line-Icons.ttf | Bin 0 -> 54056 bytes popup/fonts/Simple-Line-Icons.woff | Bin 0 -> 81332 bytes popup/fonts/Simple-Line-Icons.woff2 | Bin 0 -> 30064 bytes popup/partials/tags.html | 8 +- src/background/ShazamService.js | 5 + src/background/SpotifyService.js | 9 +- src/contentscripts/shazam.css | 45 ++ src/contentscripts/shazamLocalStorage.js | 39 ++ src/popup/css/popup.css | 65 ++ src/popup/css/simple-line-icons.css | 778 +++++++++++++++++++++++ src/popup/js/controllers/TagsCtrl.js | 36 ++ 15 files changed, 1186 insertions(+), 7 deletions(-) create mode 100755 popup/fonts/Simple-Line-Icons.eot create mode 100755 popup/fonts/Simple-Line-Icons.svg create mode 100755 popup/fonts/Simple-Line-Icons.ttf create mode 100755 popup/fonts/Simple-Line-Icons.woff create mode 100755 popup/fonts/Simple-Line-Icons.woff2 create mode 100644 src/contentscripts/shazam.css create mode 100755 src/popup/css/simple-line-icons.css diff --git a/README.md b/README.md index 02870a4..3b4ac6b 100644 --- a/README.md +++ b/README.md @@ -61,13 +61,12 @@ grunt bundle - Custom update scripts, handle versions like 0.2.10 - Add tracks in order on Spotify - Reorder existing tracks -- Add filter functionnality from tag status (found, not found) -- Rounded tag image - Store tags and Spotify tracks found in IndexedDB (use Dexie.js) - Remove tags from localStorage - When select a different Spotify track for a tag, remove the old one from playlist (if not associated with another tag) and add the new one - Create an update page to let the users know of the new functionnalities - Add an edit button for tracks found, to le the user know he can change the track found +- Replace SVG icons with SimpleLineIcons ## Disclaimer diff --git a/gruntfile.js b/gruntfile.js index 0e58d27..6961a3d 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -17,7 +17,8 @@ module.exports = function(grunt) { }, css: { files: { - 'popup/popup.css': ['src/popup/css/*.css'] + 'popup/popup.css': ['src/popup/css/*.css'], + 'contentscripts/shazam.css': ['src/contentscripts/shazam.css'] } }, js: { @@ -55,7 +56,7 @@ module.exports = function(grunt) { } }, css: { - files: ['src/popup/css/*.css'], + files: ['src/popup/css/*.css', 'src/contentscripts/*.css'], tasks: ['build:css'], options: { spawn: false, diff --git a/popup/fonts/Simple-Line-Icons.eot b/popup/fonts/Simple-Line-Icons.eot new file mode 100755 index 0000000000000000000000000000000000000000..f0ca6e8cf907213894d610112d456b7804fd2800 GIT binary patch literal 54266 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 E0dl-`Gynhq literal 0 HcmV?d00001 diff --git a/popup/fonts/Simple-Line-Icons.svg b/popup/fonts/Simple-Line-Icons.svg new file mode 100755 index 0000000..4988524 --- /dev/null +++ b/popup/fonts/Simple-Line-Icons.svg @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/popup/fonts/Simple-Line-Icons.ttf b/popup/fonts/Simple-Line-Icons.ttf new file mode 100755 index 0000000000000000000000000000000000000000..6ecb68683477ecc5aed38ec3fc8910d9bb66276c GIT binary patch literal 54056 zcmdqK2Y6iNwKlrH-S?ior|l`yj5L~}nNhD()HRYVx%b{=xqyu^#x`JZ2)1k>gceFd z4?!jgJq?l^l0!gnNc|H+l5;{%A&3-6I0-o?e+uDPTlZaiWaA|NJ-PR}&;6hKT*;c* zy?o#L)_T{w*80pbj^j*jf)luvqoZq=U3tp`;~e(`16!*W%pV!$>bb9T-1mQl_1KCv ztsUzpH-44l*hZ|c-*wTBgKq|28sj*j58HO`*>TCi*;!nP`~J_cU){6+==om{?fMqS z$+aB!0=M_P9lKqr*EV3^zu~X27aP=e;c1*3zAoKEzu-t{?;|F$Jbl#8N`amzoeFN9$m4g>wa_L=z4$Ot$4PD*i7$&pYj`EbzS+UXV%Mm%Wp7yn)?hl z>ulN1ZRcKdmX^={hMVRFXJ@#NVSRk|_xLLx^NG20>Fev<$n3jw+s|yCyEDhl+T7UL zedVWrfDXd8w(`NV_&IYLzIps^Kf6A+gd_0sIP1S?kMmi>o#Lj>9!K|aew^<%9KC{@ z#3$Y3W8Aq-_1-DvrCXi5m*zIF#P@c*m!DAl%E#1|np@kgeOddh{xjG6jNQhhDVkT9Z@725PkVmneZ;rV z_apy7|F^Au)*FG31>Ol3f=`56LZ1)s3_n}3wBnh{_f>vBvOe;1bZzvB=pU*MRQ;~{ zVC?+Zi?P!+Q}Md^_Y->)k0iz9;^gO3TxvY^quPOVDE&s}ShgyO)3x36sfMn^pNWWFkYcm9pS*233{ZN)pgjILjF zuk8MIPqODEPVgMpDgNH^yH#HEMB_!*(I?h zFD>m_cEhsYEnmO<%PZn5Zde&xdBZCAs;#SjvHHmB$<;5des@iB&5zektm|37e#1{T zmNw-!8(TJRd1>pd+e%~d*znksW53$|jq#=(kUR|QDUlPN=eoJ8IIfuu2*nmL+rscK z)t+=S#my^j=3y}*EV;#M7KrhkiH<@)%VjfCK35oI=>e86v?$pOehbAyae(1pXM0B| z917#CP*_%ETqPWciD4-mW8t7MWxwSb>d%XU67pFh^U1Ee_KjY)SP^xVS5h92nC~BQ z*>4GI$miC56{au|O?S(ukGoZgg}njBctI3pSr)?!!`@n*mn2ox4zFH)Sd(3h=NZ#$ zz1ZdQYK#{ZRmWKp-s1jiWK;JUg4x(S9CsaS+jYEq?@;Z+esAY_m6e81H|1-X;Pys5 z>&&oMOGVbTM)>GXlJ2@#iVdB730I59nP&X zS^lkWb~KM1>`5-{Ym0VtepmL#;vxR?p}4FWh9;OZf*W^2Ci$`*d5BWc15o1`cVBrL9a4bg==cUtI!d;BC#vGWr07$D~hJ;QZnS}D0Bp_ z%WPPmy{`S@>d<`O#eyLJ^k8sset+b;#7H4kY@oj9>o;VtYdusQOi@d#;>FmAAJ(me z{62T3XQex;KxIO!IYgG-2 z@d3}ccU@m^qI==G`jP9__V?Lu#%ow6!cWbVgvoKsgqj=2pJk3)5o_GEtf5NdS|#eO z4W(}`PI@z`c!kGZkw~R|lifGfDpt_tiz>~fY}+RnOx6r{HAm)MkeeKSwC!4dMQA*d zDNW!Ssz)2gpXprBx$v3s&I?oAFt?54I`dAWoYN%q=6}^B>}-o9-4ZoTXCj+!p{*X4 z%_Jec)PSLmBGf@4Q;M+4gc@zhS!jr2BzNIZTZ`s$TH(RWr}}!Jxn*af1lp)aprr$uy8-51na=+~VXp&9JPnikcy}cmDjn zwVl(P7}b-FG<#x(Jerit<|^$5v_qx%@zqQq^5Mb2YHo_$eA*Ev03 zZlYqLg`aCCba<@XOfuA2S#Yc(WFYCZh8(D2iVV>hWMRlhE-TcWePdpOMnkuc&rFu( zh4Hd3x8#N|>~xx`N`$ERs{;K!$2M#JsJ3OZ7Pa)vn{_LyZQi0q{Tg4I%v~_g>Fr0_ zuSE;GgF3zEoV=($Xd)7s#FAX+5~r0ywGb6G=V|?k&)CvK7(a8!M!v zR-r9AVx*89K?sGVwitRH+MWi`nMNr7RZ}@up@nZHi%?chJeOTFP*)xUQk;dMw4pvv zpTN7vwW^lR;eENe(cq|hD3ggu+O+(NZ2z*aw{zvbPGO>^wY&06XV-eJfkKy5Lm6O> zP%LnU2i~E@y7!l401CLYGU1;ZW)!O?q-tWXSxIx+M!t!EYocr2rF z=Jcau-pKmL_zu`3evmm^s6}xJT63DUm&pJT5IIJJioS6IE(~~qfhi0CanOySgymqs zIW^DkefcZbuR4Cn6>-b1Rr#b#F(a;|KP0566;pCHu4sNuQC#u%1%Ay^RY?_?=+CTb zu%iAA%l!;cfee=bZ6oq9@Cxr+&=!*wkryP7Dym{EBd4fIQ*v$Gs8n#EBF77BU9tjo zB5CU36RWQ8hUHW$jZb^Lk&G|qi)4h4uDbr&z1O_40MigonFb#f*qNHTsUQN*J`*kv)+KI#{A!<1B&=Y7jI;2P_e z`W3~HOhXr}l{ws7R77d?&Fl6&d%gVy$>UXBihQ1yiQs865l)0>vI6*{ovY@0xuw7$ z9HcxIhjEh?LTHJ2kzk)7kmNeh52mm}hY@i?-7Q7WXpy^%n$Qb9=P<);D6&+hIJh^T%NB4sf(blM$?#9CEDjWcy~PfrI^kzEy;}}_c=bIeTtQ=Eci)&_ zb{TSIcyMjDsj@a`8Y5plB<*M8_OG_xWSrV##iWEU>G2Lj2tqku$`|k{!GxlCtXrSh z_sk9FD*%q8V#y=L?B0qo$OUL;ggyX}Ef?IJ|Rl!%D_4VTBcHWtT-VnkNtlxfNfvU-MU(Lq09iR9M;CpI1ieyn&S46ZEwVM|HHs z15qH?NwPQ7m6RN>x%dQ+qYBp##@%Ql+w{yd%VI_d0(|}r&;0G z?H?{~tY~Pe2^hTBQse$R>in{iGDIU?@xx4HJer=2rs2NN&b}>Z?q~I*tKb(ePbz-$wctHfCEcH5CSa|--&JM$ZAnI$i{g|mK6yw?Cx

i_GgGVy<4r&(mKqN3;(>a)lY~xup%{Ub}>3bMUf5$a}smXC& zz)MSEJJ+KBGud>8P>&+Hp|r_6Va_O#<}?L8ImSi~2B9b^*Bu(8>=~A z5Cpzw$A8_v^Fu2>a>*?u`|vU5{EHx$7IHtmlZ9n!=b`iVpamnQ}6oDPMs_M`w*_Z8D(=oPcXe7U9vpwo>SehNZ z>Ee;$gPnU; zH2YjV>xHR6KC&G9O0$Qa>@3WG^}!~WdFXWUgy^{xKL_XI0j zDuX>oM}w6Rx(gn*KdKp0FZwS~ALJp5idem9QLeK80s9?)`qX{l> z2zaA7ph5^0FBEc%+|N2fJpyfHg9O(?ojt7B8Bpe+J1_?X_&xAW*bagV;3M3mg$**u zf*?t$4}ICMVe&GoS7h8#pBbS zgyPBpI<-2R=tG#o!KbI{y1MFw5-pC?!9s~XXG%EO!8ZI<8QEeiirj0ElLX|1p0ycP z|Np|X@?|6miX;yU``*LyQ+D}b*_ns6ohM`X{AwN#nvD6J4fOFh9+fZ{fkOU`v(HLw zZw>JE4%mj2^PGgO(4>FiUcetgvu1>Z5ASt)g0wrEoTMh82eHgaMhzD^Jt0g8jilOK@GU7kBVC>Z6s`U~h%dMrsyb|iLVD%KlXxvj)?~rs^BMk4{+%s?m-$qW z0kbFTM*_zNf947cnkxCTu8J5x`AYi_qW!rBJw$qPIdIsMkpDj5FtR~3QXODH&JwsP z!dzw6is0$lC9%A8c0^|!z=7e*JJ5rMMj3=SZdiFE+~h`JwT{k&uNaUU8Nf;?D6E<} z8LBZ;reF1_sW?9TW;`LgG|4g&3HVmB$YZI#f6L5|cRWz*=scKRabzP@RCoEGo zRA~a&#Br)yqqP(Mh;^JlJrT#XCM**Sz}wtwBI8eS>$uOt`%jV;$+k$@2G*WT$?b4G z1=w%!fRIQie?s^qr~@z@p)j~@kRxzSoy`RjdWRyfg*>;nh1zKcoF;M#@^p=?L#G4O zIxYuszI1&D;sW~OVguqJbFN;5F%l|k2Rf?)W`!)f6jdf>Kwwgw_$*lnS9SCz^E+$$ zLt?^Rk*ccpL=%cHSX&oP^hP7S@rt^1(61z_Jk?coA&=K9O3dpw;=AlmNxUSuB7tDg z?GM$rRYh`*HD1{;J!(U}SsC;Plvt*svk}yaBnckPYK~Q8s^#iH!qZS6t&D{hX8nn( zP{d;jieJ-Ax8+t~314$p2fVHzNvsSw%2!DWdYkuKQKLDt^nl+me5M?20$Ew*uB@o2 zFsp(Ar7DCElciP!}_Tv8sep z8;&Y|HJ7Q1i@NS}>4NN56~pD0NuLVbG~^#~RYYy0Kx2f#9Hm;;p=71zj1)vQQ%2hk zuIBm6AAkJvBab|?_vF@X*Iv7A>&fp&EwBT^)FVgiK5Re2UOe*1Ol<4PtGBQhw_JU4 z>liezqkbtqEF$juobWvSp9Zdl>!5gUdBo5yd~73NK_G z-ji?=p9dE-2ml2x4DNt1v!_#v`k%bdlVs9|Wre_#EZiW>{F>kmpbqaB_=18DuYTxh z%_FM6_Gla1CHDsoijp85G2Pc^X#}j|t$UvGN2S~zm(Tk0C6ZSY;K+!qoo6#|1HCAk z##@5;5ud=!4=X^lA>+^o+*12SLD8gJR;ps%^#YM9{1M2>$AA-dLQdM?#Vo^{rh=e3 zV^D|+Mt2#^nELD+@|fM*E;_+bolVW_lmOKEwAL0bWz z9HY-+wr<;zCndR1Tl>RPvO{FUWLTjEQ% z_KvNB@cEvZ_Jh&9rY?zjaM*u{&2`HPpAY@K3!GZRzf+tnBJ; ziP9JV&(jL$X&tzI$2(&_XfNk!Sw;cF7$ENq66o-112XLdzQ`7XWDjC0>~TIP5eJmb zlV(CkHp4=`=2bP&UVyR8;#vJ(?DbiK>PufW(WY z!a{-~%%7HhK||6t4faD4_)H|dLv_2;zdisgaB=IuzWS;&2FGWg6(*hd+G58C!{{%= zR7Xf11rDB(RtUi0-JCZkphK=J;TS|Q@BuWFOku&78vp|iper~=llUR0*Ar*KEtJlo z=MEa0Bzv@@W4c#0rLi&1i;fn?kH`VPv4789Q8uzk_b8?~woQxc+qW`Fg!nV&*0yec z@Yx5qPXQNFbTg8{r~Q-zf&~fTO2ZJQE`RBYnOAkM$m<`t+EnD@$4x;NPhNwz^Xh!+ z@rk2B@${v;42(7GGLIkU5w>IcdF+T25NLSuZ*~S7mZcBu z`tF)&-W9g2bR?T<3`?SwsI2h$q4gC_lp=9i)V!gfW%$8EjAS~ZL7w+kv}YGKe45oX zjC8K>yDNhgq)=f-WWFLAmL#_h`l#B=3xS4IeO0}yM$R@Wxz>8MQCqpDXEfY#Z)=Mk zE8%CHK7L=J`75=`3awHDB?h9n!r4$!sYNg>AjbF*F0Isq!Cfz}`Ru-MdtLo#rlZo& zoBl+!rKS+8Zt#R^od*qgRYAf4vAoe(T}4GpZCx}Ic-=~bLWW-yT}Hs72L+YNDr@RP z{=t^M#%z#}H!bej5FWT-b)%ebX;2%qRVgF9us%Dl6vyLDg6+Vw`87P2CTJKw0v?Ul zE1?xZ`B21KfDbSZy+>nYh`SPD5E#Q8btlGx(?`HLVzGd}Dn~o8wcL$`7_S6ms5y*= zfSbY;WTzbW4#S5?dbkkuMZ71_5+NH{ivXlTE+Zdy`t!f=20&5kLEXb!(MYU1WcCj% zs?k+1#s(8bDQHOsz+Y%k5>%fXs)1K5PqMOZ$)1z;MD2@r-}g-|7)ZHXvZ^xXa!EQy zSzl$Z8E$AxYl`Yta23JyOP)-s(Wkfvr$3ci(bF52E!YEXQDc4J%1cHr8Ek_OEy#vn ztz5QvXM+|t)3Ft`2nX;kpC;(Bcw0sHwuSSr+_50TH*TJH`!mnn-q4#*_VoCIv1Lic z<%dH6Hhx`x;&blWuC?u%b%VVXss`c}rakh6i3=4p*^IAc%fMZ}B4QM_62ec8NDfD83L$2TT&MsOqYSR>eGhduse`)wza1 z&*J(O18NjubU`(Pi4d8sSVz3ltzq1-9>zYfb=%d7>Z|q!YmxkL?Y3=KGye2z!{g(_ zO^X`Gm`zKWp=R_CW08hlmgCt0N##vXwH5JtRQRO25%F7w#NVFR+%8q@>%em*+N(P9 zcL+YOrvmW}9OUvwg9*QfH|^U+$t7&P`s%IGC>R$-GOOIQ#>0+%X8Vcljf)3Cj>`n0WAm=~|u@6V1_CeVlcG-z&8rNVy=EZlP zWtY5+>zve7`{AE|-oBIlkX-^J`Ec=-qaI!NUi!m`!^P6Oy}&gBZ}xHnh+-~5^ctx= zXWp>23_!t+kyj2etHxOTU%W1f+}ZaP@Tz%Q1w=?mKD@R_PEgjT6oco!{KJ-c-es<6 zEEaWLy3e#eUVWww%H@sxcR`%Uq}pRuqeiN%sV zuk-zq3Pgwzs!B)l?2D&2{_`G+@eAB1p!c={?=kST#9(!%1c_N`2BNTRy+B+5l7|I^ za-4t=G8h^`yAp76$PIoCV+)yVe{`Y!F58VEq8c7JX3V}P>=!hb=~lkfD*`#1N_Bwk z3D;)7gz+M&F1MFPgK*rwmd)7g6#OG5fpXxPor$O57efl{M?+bRun5n`M_Dt1c$(J@ zhxM!sb9nAf^zeFaH;24dmVu^)BO|vbTcBOZL=$Q&Qv3-~S)PYuU*?>_sKBofpyPQs zt7QMmIveLAjulFDAhQT281$TA8kO)40~K{m{Ruo{t}Yb~xMe28e#lJn`l{oNt-ZkV z-RJkUdoTh7QGM*?XRlv{5NG+z{+C79A6V18;EIR7_|O##@bzDjG-4&uG(f}(#ck4? zrt|4E+aYPN0&wLjd~oS1sw*mV!O&bt@6szP<5lo_S+;c`ssUaiduqqo8_<1D-`2e5 z!1ar^l zDokDftFu?LuivrhZUA6SgtJ}Y(-h{*A3E@0Jc$cs3T(OkHbOUx?Qe~(yW4HzYLfkS z2Q`ZQD)X_s$?-h<-W2c5=2H%+rI1X5f-D&AxmE_M4^UU2Bo!w*fgnr*!b7SNdXD~K zPpBQo12Kd6ZpFzEHNu0T8i9f~9XaSpx)KidC1Cn-B|13QiOs@0Dm$SB6j|n#W%f;@ zNa6(8KP0NWY$+ID1KY8p;U-v9Qd(-npm*NA$gCqFbn< zVTk8G5nUJ$boO1?kw1SN(k*j#W(u*NF6d$|og#+|FbI;LaK@KIz?I{AHDKcat2%la zF}>$xjtR_XF`qC!ak(Oj3bLP$%sc^8I6*de0ty7fkgzLH+%9c=cjnj4yfw+MzMP1A zT|ZJj5_`e-LId+MRLa?Hd}-#_yfrh$`)A%RKQng__QMOFJc`l5vA<&UCD8cQ^!x?v z1F@d~jUh~)CPuzYvqhRtKSWgg=?P0%c6!QT)dVkg+i@c7*)$eQW$tcf8f10co;EGq zTjahDuB00nw4UqdMkszLWhu}}bfQDoKvIhi=S}NQvw=Ux2m}+3pr`|l$Wv7cw1Ojw zP;Nj!l(c}y6&fA`zvXx5k@|MEz3*z(GV`<^d<@j0SK~7QpZy3i=Hw4KCE(A_?3(&;9i26b`I}xcC zQ34g2?+nQ4L<66K4@IXyIZ^f%Q4L6;X<)()hBxf-sBNnbm)73AQjfSZf?;?i$v6CW zyFax1?}t4wgR1P&?ft<_mG9+#);rUM0mWoL%NQ}2asBSI>fOq zkF^B8xBo78Wo>guPtEsg2K&-c2r=AT>hIYGR$mw#~d;x9jX`SNR)1=5AV z#o6W8F5{OPL8rZdAKn@ zdZ!(*aLCA_%MCC{59m(#ZD=u7m{~1Lh^&hG?dTsx45%ACpMSrjC{D1(t|b>P?P_Ev z9If;Ir@R}~fJmO0B;Ky8nxqKwYGhe!s$Xgg{X6UW_fVVU$Eq~*Y$l&o3~zwkvNB7< zetwrrMH>OH?SyE86XK=5_kYUZPeMn*fg5ekFI}2%9z_xmBdvAJc;c!)iC2;St*QJj zB9kT2b(dy=FHy8j_kR1^_ioY@tXTYc(M%@lvb+YkL_9#kiZkA0TAb(ES5;}*Y_JvvzImw-@oN0y*i)WqiBjO6r`Aq%= z$>W+C6qsd_%0njB=S;A!tj&=c&`GGn(RauLFKb6=U6|AkAN&TQ4(r|&A2*yP+_oNwE@W^sRo$Coao*egtK&W`mvUScfMN{b=CwSBiKmnTCmw2>-3@O%U z{^^b>VPYoEm*^R1rs(tB<#OoNi3bZ42)aHOO+SDLGh@?}VIsV}f^F#J? z5O4gOD1XgzGkGoMHHOVHPT42O+ycxHHsFfz>m%5?GF?7{EKn8+2G4(w{2Np)dkD5g2Xu(okqp z3Ho)Qq-1Y0H)??{(A?pW?+#a0tjcwfZ);EOP-~Sd6w3tng+lhyuzl-(NcQLH4-5l; zuQw2qkp9s+G}Ibui1{(Fkqr^>-cPKg$nO|jW>zHX+aqSAG8_ybMOiBm9up+^6JH1o z;_jku>55ANtX)!ww4?%p*x|SO^}b@;n_1U($P>`39`~RIDbNp!|-HQA#S;eviE@}5PdykQB~v@5xRhbhxe#vPDlw- zkC2kk0f}LaDDDBa!(NboMWDNUJs(Vj6TW)hm%vy9_yqN=T+|ZkZt$hzHPz9|P|&Lc z+eg>dH*|+uFOH^%AmOvobm^V$mXYj!tFh2;n}v%T_xF!?3jDY1aW;8+f=$}vLu|W! z3N~*NX=f9%N#!t-&|Hdj)GBUYC>*VfhKzXcP+e_DtkjZ@K4)(yTm~okR4&O!fE1H95B7v!k5W#50Nj{gsA;L*vR< z)}m;ZK2SdxY`_>fSl)7&2W(n6$(%uNmrN^!bh&d@*v~DKHUra6nY?U&T?uNMQCzg* z(4hwoA|cJ|lQsJqeo?--cyTe+n_vhxLjjm!rJSik3!ic{!j-HWRIuQ6&GglcT{AW| zjG@5)!hW|L7n}L_R8QZ+zN&_>W80>H8(u`-T|bx}R6c45=7717!3KIL8=3|&Gy_wL zPK1LZ)rD-atxz^1M8;>pX5q!);-Z)c&RiC7Dij7=%Vgn?_N`z};cq9vLa@izZR*PV zzz4|xYJVqEVm+_wU}R`_)6i!Cq3VOtt|}i=rR_2MqTIfnbvs-jeBExZ=1m7~4W(-9 zVp}a%^FDq)I}C0Y5$t^%;k`Vb^~yfgE5B-gbF`8bUqcF}tVtib`7?0;pgXb4Ud-;0 zLxsAX*CcpOYEk}_j4Bvs{Jdn{tsOPLgtQ7_n-pLtSi+l#8h$r%C zMi4065zae4EBRvNqzW{a=lG7fOd3=gP_j0}6^dJ}bY1p+#y_Kl^`rJX8fd(?kQs{I zlxZX!%tU5j?2%JLn@&$Z^C$ZqovHi&ncwof%BvSJ6rC~sr|es^95PRFU_L$9Z=_?Q zPL>Zvq|;Iw-38SbNX)V;ti|9Sf+sGBWT1*4nkTW8-|m9%+@g3$KGilUDvju zlrHTrt+#PKlOs$)lU@rEK5%%eeB$wN#mLc1sMQ6aqlx+3eU32}A|y4mb-GEdVM&_d_!vkA*B@8A>|YKLtN>w8*%S z_f07)d>Ro_kj3TmGQxwdV63jKy9VV)BFZgx92mU#zRhMd8mWv_RO&8c{TSQk4g}mY zZp(7-`0_8WP7Q6?-@WkU_Tt!sa*SZHw@KwZNGW}+_kQ;lM8X7$1d;Px2)w+ zytfIz>pAGp=fRivB0FOhw-J(XK6f#9ockbm2X{aBkkb|-+JZ9K;CGkb5(zx`gVVvg8| zX!x!tPb}S@+o&r|i$k)J^v>)D#TD(IpW3k=u2ps<@2M;dCQD&|E%e#W^75BET zOIL0#oVQMKtpVkP&}lSf$!=dTY%IET=8tv#zSX&T*Hylo+a?Oex~^DHezCv1cIo?g ze^s-)I$v!pW>fPf4s4rWFET1|S#$qY+jC4__ra%!Jg)f*x{651;}felH?BG6PbGbL zoF6k~n9aJDUAhn}2t0u0C? zrqk1tj&Y~G{Qsm+)7c=bv}kvEjrgxKhka_2w$iSM#ZS*&k=7xmPVPna^Kt||f z$u5y%;7fV{PDy;3Rhpa`XH#~GL&@LlchOd#<2xH~jHy*u%6 z)HF!m{J*vS|M>jBw(YRu@e;M*-~O1n`EMWNTt;Xvr`4VX18BI<`rUE5P2BBA64rme z$d~@s5$9Y&C?QvO(&?~q!V%-bIB}xF>=3oflki=LOe{kBqd%3pM-^%Z-yex1V@$vm46SP>%iE{hQeWEXi&w zRT?{d<6Vz#Faxq`%2&Pp&Bu-m&s@Ckj(z*?m>StJQd6vHhn?Z(Vy_#)XPhA23d{+e z3MUk$)DHJKM^ON=LA;bcK#2BocpED7J&9BB)^tF5X!0mMMFyr*Ayi1?O`Y;o>_(v_ zf?N`k8Q_XJWx3?&z{3$a*|p)C&+fSHXE#x@5$i~;uWb)yE7of&li&A^BTu|z0W0X* zy4v=N;7e31O}#$Oy4hlQt$ZlT^QYIXM_B=4RO>YZIavIEKrBZ#TU!Yiqcj?^pRY#0esl63T|2VZ+kiCcU}>WASv*D6UvQY?uSJ3-M1 zy}t5?3IZRvwD^i@n(8a0$AZXh69JFqA4yxi2oasY4{swQ1RBB~Ndz z$&_57ow8M7nL=TZrPPc#C`N5WM`fKRq(pxBkJyh*1u$Z?EYUI!3kA`D?wkJY#9y@^ zRp3aAkDS`L?w(7RYT%A#ML;8}-eAPNF4f^Qo2*M{HN1o&Txw|R$kB)T#}XQC*GO^V zrAyyyGy9j+Xe^9YL*;^J%L9jp`}W+v*4;#XX1|359RxCg+y?*Q*!35!p>3h*ARE28-^l0VcqmPZwN%_6h6pBj6sqaNv5 zN~o9{Dzrvw37BM;UoiB@kt-gnDfTUBAKdo9*r(Zf%ZjF{)hu1TZ{LM`8@l$bE2V^o zi|_lwr+O}Z=%GvNFS_KSix*H|AqNixJp7ax;`78r3Se+3#sZ*;sW7Ok;EhrzbSW)orPO{gQA5 zK8zCFIoz})?*&PCquadyYs!nx9gpEVkk$-oY{cN}{9yY9GTQ%JE@&1vT z-a0%tFydS-lLvF<#KF1MGj4i1 znx5r8dHO!b@Wg3lYG#_n88`JwVJaPk`@)sTBt7&H(Pv!QJ~8g}6iVheY^63N%q9S< zQg#CcHc{l0p#E~KIUb5k5`t(6#MzucCL%;=SLYGSb_$0lzztI2P9j@O6tNl>{`G#kl4oWlEpytmO8Q}~LOUdh_Kyv}-(X26VYlQuD0@A2X~n9kNz~gyF57^=Uj`0s z;@aol2$-5GA%&pwUJUNN*Py3Y(7Iva>rd%!`}aVBPRS6+Mil?q4?tt#@psS@yHE@K zl*)cGbCN%HCx7RZJ`ATELym?f^gNhV;w$X|=lNccq$w@(DOQ@qV{w|pLn?T#C?qz) z)gpJg0p3y@qUI#KC>T$+C)?5Hj{Kf+1p#qW#dIgaoG9Asv|P+W7j$YYoD%-)Cb`PA zpGAn|MYBr2Nk)Cxv z$dmj%%w>4JhW)M)*JpO3hL68H92K;$M*pt#s@tkD`I?f?P zVaUuuW+$TQgDcsI6$gV^X~n_$g|C4$wU6hQ@DnHWV$LiqUTi<(usrsMov$A_pM0E^ z$oSheD-RxAnP3N3Z0c#T$5Xw@C3*I`eT*?~(SgN0;tP``fdA`{xp7LoLYRx7p1T=Y z%p5}~1a2%99l%VAMXQiOT&$Qa{x=CB{HylYHSt!>gUNFc4U$<03T}7-3$U96Rfcb+2n$rfEi%#H6X<;Zqh0{a;0=5p- zOsG1-0thjng(55&mNlk0&MXi_zAe*Uq=^JlO0qrc%w|H>F;F9+4&fJKLswT53UpBY zgWwY4|AMu!f6L~^dOZ?!U9O`3$LCX}aA#edv8uM-#q%@0M{c|YDHLt>jeemfq!eO{ zeVc!8|MFf@UeW-xfEMXnD8rf?rWy#hg6K=sR(GJ(7@x^#L#int%u(S^CFYHdwNwOr)uPh5 zT4B#I-+j89pvard&}9KZWO!!q#6VJ-_HQr^$zSx>?f>uf7Yp_BVNiDc;A|Ti@FAt_ zp@tavgaBkmyAt}}-d8UufG$6WS*4Bv4#BJ`NC!jj(I%e#Z|bEN5$|!yJ{QWLsb16m z6Z#As$(shkps+FA?BBzopxnR<`fa*0-=vbquj;P%lB zAX244==mD!+x!C`y79=7vvL{lhRLf!JrSgfjv;x@Ee(@Ed!KSu@bB<_3z?$TpBXO; z{E9BH6-Sxu46d6-E?Qd8V|Xc4x`#PT|CTuR?R5dBKRbajUs8Zr@z(+&L%*n3_s3S?3%h04w5W6H37p#8 zoud9fS#%RCf=|ja0wE*Kn@67^+(KhknSZmV%HI>^FUTO}MEwHz_in^1R)S057{)Px zj!Y^X4H#|67JY33lZU?mn5k$U64jwMW+ITcu6^%&Sg=f*syoNy16>^r$*{TR)^+Rd zt>c@oLmG3_jU@F(PqbcEvi|s$^X4BO9zHz3Yu88}2)}Dbs_Uxynasvkc+QesTwFqP~){(5Asvd^iEv!b+r8hzy(>v3VVQsI7;Ly zJAmM|fo{$vlvuj#>S2#WywZCII;jE(h?dPJ6fqG_vs`6!WU8)fmMe=71yE)_15tDP zDJTI!n)FxuN+|xBWYeXR9cSR6N(hOtae)mWIifo|kcqrrvQNk>*yQ(Q3W*_y1wV=ix4#%5p!`_1=y$>VnfKAK1 zy%%QoWA+P@IBg&^-|%Ymt)k8a-%8jluVJEwb5^IPA+jj8GL32MApcMfNW!${HBiie z^yg`0&ijzyiESq4(r`8*pT>vD7DeIN!?+1}wMjQ_x zw%{ehhb~&_Fq~!~A&^$3f#1SAo>X}yn-Q7i-TfU@A)DV^ol`2J0sVTXI{A83;i$sr z1fR}6tNVm|>}&3}zsnZd$Is{YP9<7fhg;2H%zinP4q~z1&)*gOw5h7*r$1K#!030Q z2$`4Mu9+jci5m46o;Ya_vXe75j%`hNV?i@i8^T2rEn&({I{R*M_*PBuXz^yL#9zD; z!z(${Y8EZLO#&K*ULi*bOpejPn?YVI@64t@dks-B#Lw|y>B_J19p?oez+()z+P9XU zUO;BF>Gt9k&Utu`XXc3P0td)murE0C=)axXj(KPF^gc}h4JSoN6Hfm`}OA?&wmP^cE(8z znqtq*l%D53C|sTrki>zRBn9@IkRZ*0IgVnU0r*~VM|Dg`(`C(ZdYYde7F>QzBQ-qC zkT^~=NeaR+<=1dN=)z0E!i)l`!imiQ;-n~G1^5GCi4+J{K%y%Gc`^9Om;eCtL5AUV zz%%S;8tot8gm>ebad_8exXRYc2U?~rAKJ>W^#EgmiP>v@610fzh?J} zfdey{0n-o1R_X~>+6*#p={$y7W#TWk7)Pw@!vRVVAi9Vhng zZ!&PJ4|;3T?M>0!cHLu{x7xq4fAtY$vxxjT?~?&n&lNk`A^!t%Cu)f(_Xln*5+Z{N zFdX7aF(m&ZK?nz+<-yz2%a9wDbY4cROfTMX*H)JHum|ui_6|>)*>8U6%zN;STkqSu zp)+i)Z@l83E#NAb*-wEG)$tx`xpTG7{{nepx83=W z2RV&6Ak0#3lX5Ont21W_r6r;OoYGN{&sS#j%fyz$WD-0A-3`W~ znu#dZBWhh%ki3I*<-vwUjYHcom8eH8F7L8S_`c*vchF>sNJJDS#)jnK!>4wC^DRR- zM)MHb&onM<+&1J)6Phg+vur+2xbo>e(jWx~EOSu1W#3gxE??dB=v{bD(gn|99-ikB zn`%I8?LA!pGLA4XNd2>Fnam~Vmvj#58haVuDVc?0JMnrT8F(Vr^g035ro}=w!afwq z@=KbC%d-9Y6Z#ZHL9h=CY!P24BsK(+RznRyNpm{7}(>fH=(ST1-*@&eVX=W~k215p-w;Orv@3YrT>V@^xa;E*YR0v<@@ zTH+|U3fi)zYwT)&!D8h@(>%T#>=tTmnO%)DDI?qdRGbYh$vcD(KBf@=PZvlAC>A#bzikS0QU;v%?F7zQ zfQZ}%s_QzpZY2xvES1Tk zrU)Y--Rb6?@+NE#D1TPY^2fsXvL5ft$==eT6}gQ$=VO+Om*VlkM5(&f_j2>e-Wkvk z>_tue2Z@#TpNiw)DIAa(jF)bWVqaep z?Lqmz31nA81d$skyN6 zKFr#U=fYG~_HQHu(`(Uf;E@}`eWuHcXGg$8l?+BlW8Z!DZ)gr%^c`{gG+t}L>|e-% zMWV)b@Ld>gG&Qx7p->@urVaXoXb7~lbPgq?WdDp3!ogsK0wI=Z#Iy`fl}#}WTN2}8 zC(6VhIw@7mNyQLA2zofxhWnu=GTfF5l5_w%W^p)SeAv)M!jAx2e;{PMZQ^ zpbOFLPWNc^TJ|4^3357Eu>a`nFZZ7PM>LXwyg;U(J%Sy$D}*BSpJN&?jnanEN9a}N z5^3rKLD%{7-qc;x!0H^y#7vhDmjgd28P#VAAaIs6fv^1zC?J>qrXgwe+vozw2)c{e ze^R_Y0}%tJebm`+x^j#K0eMZRBA4OZ;$HhtsD;E752#edxI&dUbi6Ip1CcjC`*4^N zI}xlWSDT!mLDtrRx%^Q1m7?rAnl*vEBjCMYLMv~>CfM*A6ecJR5kh4RM1+)2%(C0- zuCrgpR8!Y3SCILK!mKk@R+#TiL;RFimcD79Z~rH2WA`Ep>%`e=uw`GrBzyENr?d+- z?waVF$G%p4x-?f|h7vKS!0ZUkLhclaA&(rjPk&%X&X#wLvd?vWiIvN^Ky8*!!F%KX zteE|l@L$3gp?zUL(ja^A;tsqmI~sYPB6X70rFq(cYyq%nfDaO+LgV0t8(@b;S@^)| zDaM9)8>kk}EoLsulx99S6uRr@S8ElQ0mcDGiH3r?kc6*%g(i8!0J;?QU7B!*J@crG zpTHbgk(miN(U^ttcm02N?Au4!WthtvHSUxjDhe;WP@b*r((B$LJ?PjC8cPNE6{F59 zlRyKQo|TgM8~{$HjhqOo3I~58f%hg`4Y~yLe9W1@T!x=3eB|bg;F)h>JUcIBZ$>DI zJrbIq9Ug03ynZp%ByX<{^{0lxW-n`FkATi-T-=DYeJi`VR(8#N!};Pr=zK9Q6?-KZ zG^55#HL0;-9EgADY5WM^tE@uqYyXeH&dJz{Wqtw|=vrx4l)v^mU+A58XPv`?^uniLs(u z9#1x$^jP@qYa6Bua1|$lS1L;o~rDjvhyrE^OtqU|C zs@3!gbT&eavhbu^zszv?pEJ4_W<_8)5qkY{%;W?^l$QO67X_MI{2rKa{(Nsb<6pSX zzsf~(8PBg0q}wo2rgZzlFY=HvY;vcBs9xa-sC~<>{kX6!=nuOXlKe6Iqvh{#SytVa zgUfA!j=J=NHi^fnY+&0sA6rXF5=RFg2> zqs$UZ!OP+4Lkpxru(>j(k5+ZCr_Ygb50Fs?94$uzjXGYmKkwwlfb2V4=A6asA{O;9 zsjN;E#0=m#Sb3!EQ4eBzT6lHk$Pfkz&)rCsyf^MGmoxAEi#p~TR<3MuM=C-=Em*`w-rw9Ks(sgPT`wbj%L-Ef`2*D=ILvOf3 z_5NxTQ-CyerF^R83N;2AI!Bi~{ONNJ7R;iil+bK>yi%Q`;|1Zd&j8l}nTEGOaCa^_ zC;?oOP2dH2htXYg>+bHM19z`|?8o3|k9_Y$``FrLrChCb_@!yTd+S4&FF*i!vO-}c z`;_sun}uD-sO;Nw+uF5v9~k<##|WdJc=l^_tIlHp3o;tz(93w)UUwxSpM?K>S!{z`?v&OQ=I zHNbHGfA0st;V*vve9O6O?=7EQ`m2Sx4S)OV|7h;Je;P0~M zy0UDc`lav7kFGqv*opIRSCtmnJP*Z(k ze(Cs6j}}Tk@|`>Vb6;B7f8z79D*x|?T7xco&O#HlqJ{QedA3lxP*>}#T%9Whl$XuF zY%MJ)SE|j?2YBecP@69D(yXz#91eHNl?0oXqv_;>?uG?psZLkSE^(MSZ!lyB0MR5| z8WJ~2E+wj2-@s`mh$;3i=<{%+5}@Gja^2`keUQ2p8}_!V;g65-zj zICGKLHSUlYX%0^RBo(fLaeHF%|N)ay>QI{n>SXLMx5cgShy+z-oqIY!U%)#n8LL&1SEpR z63)t!*)9l=>Ya*V{wsFp=7XD4%hHk}oVs)V*RjouBlRi&=Ea6T*!&TjI~ni*j|Zj| z47V-fzt`yS`pqb6H^q!GS+ZCx65yRxQ{m#++InA6)Nf_NTHy#=hLY{8d$~gi8Yy7TZ5~U1~{b%x%^~V|T|bb}$QIFmM6hOpfAJYwO|wd}X>V%h{2Y z-NDR~0`>T^twLVu;^s2-cyW!tB=i-x^~Bw$T+vKrb6xZV-FV`eFYdYl)B$s{mz5 z>571GLb3v8Z)zbh_?*#!Obez%4!>-+8N_O{%j9>cBb^H2P6DCin32Rx;|wHNM8+%Y z*c;&G6E<-hEEKcB6gYmH-REHM8<4SrnVn|U=I~vVI}aEj!XjXbuuuri1h)!pX_vzN zH#FWaLalQ1-p89mDFH1M{5Oc{g-#zWOfxQ@QVIbwg#iIii)U{^7%O)$1#d_VnifjP zv_JpjDM0E#4e`bs5}8jhzEi zIF*xC-+mjRfG}y`PZSHKG5Lqfb6 zSMgIba4jyK*g1#_r;lk;b2z6g6+{roJt zwB6(~vG*N*FTEtEuqN~s^vjra|O(t2Kpig>*BR zbBIKQHM$g$RI7BGFisGuwN*oU#M+dUYYwd)_;>z?*GLq<{Ju-Es~+F(Hfjugh?DcX zdBe`(b8hkz%fBa3MWl1Xr=YtOEfLdJ5#mu69+4{%VwZ|?OD#8U6n9EKxaYVZwSY-T zvag;b`gOCQ2 zqREB>+`&3nF%WvCU`OD1%eX{-v3$?dv=l=3@S%36+rEAWU|cLdx2?+L+Wd_*f>A(x zm=)-cshG))N&A;=&1(>f>4YPB1@vVC66nc+x&QH0nImxUc=56=_1Hv^=mUNOA&OCl zgDm-C2m*F2o0lcw;!jR*(9$B{${BJP3h$|}C**3uC_CV)2NWK-ix>bRD6RX+9)rc| za<>6Iz)E;vX3FZyJs1T&-~*o$_nIx$fQ&U-)(+~Ma8)d zxalA*NzQ3j#8KLaF8ol= z43ps#jCI2q98QHFNRmnAqzBF`l0Kuxt!~8eNiyO^+tgW_s_f?(C_aPBRS2SVj_}qG zk^N{~y$1>|#6Mj0OLyxMutPT>j<18XlUQd&3m7zehqf}moU*f91*V-tfkH2 zGBWWcr2@?CB_Hfrc#xMttG^vKncG1ZF_~yzrrp+`^J^zouv}JPjK{bkMJPfeW$E&I z+}!~RI8d1ssu8uhSdnasr0IxYI$!oksDF4>%G|W@%yXc2CH3_s{7Y2eRPOYY&|vZ{ z;Pj=djwq1U>em{b=mF{CWdIf$rQH4AM9@~hmLQ|F%fe#T$EWF|<`r93nAJ!59PTr( zC^P%qZo=<-mFK6xrlA)K>Eir9@S0E2@l$%IAGx5)R(t`5S%l3K`;MBzVq7IS{HJ); z`G26gLZO5(4}TO+?x*Ss|AP7|UgDJa2$1 z1UbH7|4RW?iS!~I{0`C2vI5pc0jyRP4nc59gQ6Xw48Bhd_yO=tJWmr> zxLHB)A_0aU?3MHvWF9I?VX@DjBfu6YhBX3g3K}%>s{p=P7EB=|mIH)0b_Jw~usT&G+_C`T?*X|**%lVvH9@%Agov zcO1h8Vqs^%O>Ld~Uu?@Dds$M4%t`z8>aZ^)zUD?=2Z|ec3vnZ};OLVe3tE0G=+<(J zM)R|&1EvOq4w4z`;G8LbJ8pBj9P_aNVXAVQxfBrIH8?TN<8wb3&ck0n={A_uc6)^V zkfz#P8)|_R|1e@#+kx{0e9X7lTXVbs zuYmbMvn)vq8u;PJFNf8LRtrl&);|O#oDp3>fiEFAE_1{xSxv}dg_IBKj|%MA*i{*( zOufU#{%9BesD4%b>IWYPQy(y01K#B3*C3QSBJTN97Ox8dIg<3+&Gh!HNsko-Oj-kL zBJ&mAbUkFVFTh_(07it)jh-MYA7Iv~Yv^rg;^N(e#A)H@5TMZ;EUp0F93bZJMv;hd zK1S~c$j)}PU3?S2J$EeWre9=lxeCo*pftP%6*c{qUUv@hJi9S2PIw*^f$r(h0a!d2 zH&vj*B%C!#ti@azm>!N&fl_wvyyj$a9^85PxpQpx=QIjGr$VEx_=zXPSt15=d_NSi zOOl&z8x502CB#__7nNNxJ`ESgr-95;7$9;F;n=TyhsWSR^~$FS6rx&JzM`%qQX z8Ac;ycdI`HNG%{Z?V&JWJ?yMlG9uF+RMd9%M5le*0XZ*-08KcgdUqh}2X0R8} zpDMN+Ot0N+bl72l)MK0^rx!A(gae8C?$Vs5Xd@k?9)fQVZEx7wE*q|f+^&I14->lh zA{rl#j4JIa93Fh421N8I6%6R`#8@h z&y^o9;S?brCJEmZ2WQkTT*q`%KfLTjSvcm^#GE{$6(&P-1cZXX^B7E(bLYeburKQ1 z7*E?mN2rXKyeMwW;84qhU~Pk=10v|~qPeRi)L(vl={~I{={6SfHq=3bHbS$8xFC4! zk(hx_^~1G~FLQ{%m37qZBwGOG7M)BEeVjvr8OW^}@<5~z^FYyH&uY! z<1|SP2%>O$BQB@m*+UynbSidd&?C<7e!8cyt+qHFaKMM$6)@K`acAY);%#eoLf=^L zN_g@dzGSkpxu9xi*Rt~9Tw7gvDUzF%Rn)Rm$XDu176ppj94@Q4s#dnjc3(VRQWLrU zh4m*lgep>zsE5{-taE@S{SGwAjy-6BS0=i&K!Uh7h{zJ;t3N6GbM=_xJPjp%WI(27 zh+f$)on3ZK!1!KD=`}wiv>nA#yqNV?UBZqDOEd~bt573pG_DK$y%{n!Px~)Sy#d>3ls^G z(*uC0amJif9#?oV0Ynm+p8A@Yh0fVYVOwiz=+*1-%4MFF1~z)XTKKWPfU#ZpQ# z-UO0iu@mPInrV#1#w+H8x@U?ju@uo|g;&AbG}_Z{D>-or9T(x4Z@W$X<&_fyZPgTK zP`<)}gS>C{Q&YjoU(we-@M>4~HC&B3!I7Z+0HJ}Z_=;|w`^Knn@WyBOY(ScrlPwQk zr5#`HL#N*M%#HIW$7t~XK{yCqKjC2`TAKD#4J&a5I<^%!hgJq087V<2Oa^WetPXiK zxw~M_qa6N&#Hf*z75IC&wm@8@*cq;=hiJfV{r$HO9{l#9kj-OF_$9B$Y( z-uGbt7Y2HtCSz**7D=`^4UM%9J<9GJ9)sH@wF5fc>!4qY!~3WU6dt~#Sm}DaA+2>p zftJ8Nf&fnxL!p}{(VyC*5Rs=Cdrr}4gKl9I3c`_uvE+^l1yDHP57{maKl!P{OYSF< zNSVnH5#BElm;TT6U4?N3-5|IrjaZfH6{6awc(%{ z$4Cih24=$%xGg{=6k;%)oVn2!aGBHL`LBf2>F|+<;Q{9pTKxtPIm*EtL)&(PTP#Iv zHPE72A{{3XL1GW_)?5Y6^C>EE3S9yFT+;x+7rrsYNJEO{xZtKLp1W_IIinGhX z(*K0s?Nh&hD`kPgJc3$Fcmir>^l__^a!`H(`4DbUzk4fuu&f`rr;~%kQT*U}|3~|2X$BwNpK`d6j!2MYC zD2AwnX-^pZDUlip(Fi0Givfg6Lv`@bV8C|}K8I=H-wRIfz3nCAvTJ6xgo#kQY~dx_ z*VaV@cF+EoY88L;o+TD0-cFK;R|B9SrT8o$xf5F7suy+pp-G7LL?OeZ!?>t#f#+(&e2)`vQ6gea9&`R(mr% z(a%bEb#K1Aql0K!2@gwx8Z4Q`f*Thy49_Ehxwi9Jn*R7#> zxTfj4=Ga5(=XnoIar={DIT(kW6D4)Xrn|TD9>IPH0-5WPt{YZSm&|N!99)UXpX-Z% z)*$j{6r{$46|7Tzld~>MyU0vtaFh85>+l}%q~qX8t-uutfa+aFr{Px#c##@d!|_k3 zBnS~$pLWA0rJk47*O(50gR_t}Al{&SCM2;KVOy!^=FjtIBiGJ#P>sj1?-g*o3NQfJ zCh!ZS{_-Qexkg&S_AVBZa8e~id!nt720)^mFgS~_4l_&p+-wvMg#zM?S0j^TvUF}- zY*RO=8x7*MbF)UjgFR31GVFQb$Jji?dLJ^0!l0}d0QJ0jc^R+{MG<*g*cLWF>+l=d ze^Z}_V>Y{51*2caX+OPZ9DI2cw(=}`0rOwe){;_Fq?aOdB`HLKEr%tOss#NkINfYWvjy)WeiLLNaMWf5cz)qqc`s;#T9BBxI*wlrrj2zpKs)2nLo^J`Y6>$cR4 zZzx!t@FXgNbscNj*RcgJSAX-Vp|Ac0^mz2Qf_a}B`kQkZexmB``h9EbzudLsxw}eQ ze=ppKM|7>OiPmhXTfa}81wwDIBH`(%tLr>T&~)g&$6o4scn~BUn<8v3Am(&TRA-yx>Q zVw!1z1b3JkVxnUmrD!+@^T9l(Kf&W5hdkL%--j*dZumWduVdAOX#saS0)CdET7;fW zmo5iR7Aa*h=ebrqs;|QK07sDpXN**W#nud!XYBD_x_re7Nqt;GzIvnjdct6* z{4A2NBGy^a+*e$h$&?y5W_y>1)`wY-e#@4!-sZaLx}&{f*WOII1L&4kdHIUBSL)T9 zkgJOgBn)mh=AtiP?pcu^X-gHCj_(^UHEtarT^kOw8^tYuxVEfxW&I7%muYg|mw{SR z0DooJw~;MlK{-Oy?urD=Bzb`4adEebg)Hg<)(J@BLP{HBo-ls?E7$*6up`yN}3O=OIrp)Xr&9NM|yG+miLH8MPnVwBQfA36GhZ{HVBv@!o*B z`yyysCFkQ16pD3;0YL1U-&>OtQ;`XCVVeeP2YwVQG@V=O6lo+2_Y|}@Ru_fsD-^4w zzKPiL#|YEm>O>-w{N;#+nMk9tYno}J$LZ%)f-ja;{KMM#Q$lu@&f!c4Onkh@Si})DWo?dHjS_~ z6!VW~pVm=SK`eOznbtdP}$=fALn})=0 z=5Y&0Om7-op_@t$ZZ0`mve@aZE-AmJzOq!#9}% z!LV)d=8~H{brHW*V-4lmiaVn>EjK8?bwmAn3xq9`de{^%TpBu3viV@~@e;Y(({ilk zg~KgJ$`3aR#nF7W_(;Wxrs~43$Pt9BNT_rSK_N9jx5>wYx;rrkDTeJF#x0L~?<~cd zN`Pz)MUCmtD279^e{&YyI$0IP%Kg zrpsH!+XHi_)*dA9bZPBzC4*>>mUEWyH$0$lI!V*dV=(v@(7>ao;0{KbJaXgl*d(A; ze`Dv+tAGE>(9kO*5A0+0m=^PQ?l`+`+u4`TZga)Uo0fJO*`l%2BUIxjuMG95UkBIy z{`Rw9I=g-Q+3o4hrOoAWQZk6hcT2uDM2|`+LRkBCn9eT#d@dm9@;4xxnwzE!X>HNZ zM7MG?0gC_|`3LGhb}t=!*>D3_|Obg z9W%r&5po6*E+dOfJ#_UW<4eb8e=$4Oul|y-9wfan^j)4R6!}f=+>jQ(nd-c~tJG=}bA?RoTlME^5XzgFe>^#mbUFng&3*;1LZ{PZ@Cbrm zy%%9J0Oc{Uu_C>`DA`x0<~8aWwV80JzpOWb?)aeNEI=o4%OC(Na##jr9!wW7I9#OM zP-vx@_#4;=S2(RE5LriI%=jdXR30;X*d7q)xBOK2nVRX#RsG^4d(p*uVFcGShi+19~(y2^H6)wiOzw%mq@JX?^B|L&R5eRC_I5fa>GRlC}E zc3hjj_Mt5eyCzzH%pUV3Dw{as8*$_6Z0BAO{H19KLc}-L!(RDg%!>-nN2ExR$qo6i z!jpX->>f|-$33Bd9!X|?&h^&|fT)9tNYh&gZz63VY@{aT@WGJx_ILWvY=sHTm}*#6 zjP!bWmY!o?cR_XkU-~otf!B_CW2cY>i`JtUM#f+R;2!jg{`bxO4=?rR-`f6QXW3ON z8$A;8-OA-p4{rT@$74yowC&Rak1*e>;EQ+D$g1b83-AA`_)FMZ8woWjSzEC1zL(D+P=VZRFZdQJ|Mohd`rb>G_<=Y#edw431*j= zjrVs*E@WS2QZr(lgxc+^^Hx`y$%bM!SOVttlfjU^sPx`#5JWZ_5RPniJz&B&J6n49 zHluR`0&qpqY>^)TaYiYg1y4XhYW6vNCF#O=B+tCM(!YC|*&rL-2Bm8}SicS)u=n5z z5)vovAOXi(zpmnr2MXI8%UyE7Z;*>mY})+Uu1(W^R-(t#T*h@0o+@;pL==nY2ar6F zmzgd;2U>Fn@ZAfNo;w~`J&}+D7N@@G#O7xnCtpLRtH6H$cbFfbm72x`J;?iqg#X&g z8aUDsycDuPX{a^%IOJ~Cnqi(N=A}u4{-w2oUc0dCg&Wq6zrBA4CFGFY#)lc4LE-Tx z$=BfwafWh3{|misL-%hSd39)WCsvJQ2%5KaPmMjD6ld4o@WQVBe|wXuLXWODN>Uec zz5vt+{u%chpkR`aQc4+psej}B!>vQF4qx}|BIJ)$OnSr8cSb*d{Adu3qS$RM{&zt4 zBi%1_zedZn2|%kD1m2FdOh-_TaF&xio`^REm&4DY{KcV9>js|hN>BU`cl`zPs%5?+ zr(R-IIb3iGtBJDMa{x4jV)t?dN+MMulpDyRl%OCX*aC%Ax(;GBB9iDFQh5WJ6HLk&;8fquogzmRXC@N=xYq@A6;%4jbGM{da&^YQ|w)i9|eR~8aGN~yfI zytwRj7xX3Op?F@X0IvQ3LBwA$kr#;fQ*}XXbpUwHe84QKcR#Uj-}<6e zwb4@`Uwb=*xGNG}Ys)V{`+#I2mqW#dStPQ}0kq>P$!e&8n#_{%mHWFQL6?*awgu2j zdBHA!xi8~lwhF-4Lmz)tFy)RoHW}^ZHs+$*YeQk)=CoIaUR_|(xW#Xd4Hy-qy<^Lq zNG%{(4j?U=Y}l`<;GugL1?MuUSTT&``#c3vLEkKhp@cUte;8o{CDL-I)o7jqz!wzE z?2yG^aW2&tBMJRr%o?yHLW1=4__=YS@Eg8^hIQ=abnU{w0*}ZHJR*y(4fE42DSmfS zayO)t4FKo16YP{CBPA+GGB;0fq&FBGK+g!65U-ty zWz^r_e?PNj-YYLC%IReK}OUlFS@5G8dK{5S45NCi6 zDH0PRAQzv;?`EK_C29i371@m;{c+nF1)VIYmIWT)3i*%+Sdz6Zx4FW*40@$;FxR_o zgyNob?c%K`Nv(bIHgG;#EctCSQ4}VZ#Vr| zy}$*B)}i~a8h&-C2de?t2@a0cz|>!CJG*^_)|`eto0b%a&%qU5gGaDXy!}ozjnh36 zOmr81DqCd0C`b^V=fAN1>^5r8_D8oBv|iQEaoV(Y6ak&62D>MXNCML)Ct1MCl@l&N z?{o46c0Dd)Kv@b{qBx>!Bxf8UB~CCz;`wgN*G^$a-*I-gp__K--1(`mS?F~BpVjx2 z^+&$PqWhjihLNcG$$iOHi)&MPB0GQTYvvxhqet0&mf(*so}2j-SyX+mC9fS8x6UA!uZK_Ofi6*B2b@V3Q#RFFi5Bv1eoH8Y9$6|%#n#3T&0T;ZMOF(?g5 ze~RFKG>tb1GLHBPqJ2uZ+v!l>8Y*rQnu_-_c#B8|#mQRL|4F{j;*{MZceJh;sY+i_9zekb!3DSJ#a*oh;!&e{uDbAhg*9_0hOX8fMPCZB&B z=}LWqytp_olP?KohtX_jmfbCzYe~!o?`cDuevdW9SVQ(%^e4wTo;V z*eKG_OA%9=qyQ9xiPPFf-X;r86SYz>r=+Xnr4$$o8~YN6Lw$4aLVFEH*eeBt5iS0; z!8B!Zf-x~#4O1oqa`Ch$+R`>JwYs7*1$`v6tuC{OR+m`vV316-l=!!608c9-Qw;`N zmqtVc{i~@ayMyA>`T6t!Uqc>kgbYeu{{XvBnaNMB~maB`J%|P`|+u4CPzG!43LH9$;mE8!9XDdd&8=aI(<( z#rf}=6*n4#I>+nkcXVkO*;Yix98DD zIw&!r8si}$D0N>v1+YEU3h8Ab;1*B20|EEkm|J{V`{vsfqYL>Ke!)Vh#=NFWQ1vFW zf*VqK>c0n4fCmHz1K72I`miOH2XIYTF(sVpM0WFqZwbdZX5ebwZb%<>Jb7Y6J=PtW zRwAsDRv8Tl7AP>2(DSgadvm2YLrN!7cVY!Lvr0%7B%S!3sNkjBFl6bv%KAEtBp&Hw zC$Jl|l~k+-t=InKx-=FT3&DE;C}wc@okNZGl6+)agX+fC2^{J$U1|i*r=YZ#RIV{Y z%#@e%gErizFKrR@Rb_}D2JfV2ux_OnQO0;Y2AU)jQu#;>RRnKIM0AJ1kFCpNa9EIQ z)4<)nGpt>#Et4&m4r$C>tX=$9p%ps9m4qHeI@(kMzHW%Yfwa&}bjM$bT?P6Ut$UD7 z0~|D#AhapicT&_>0@{w*O5#Pbrc&!0n%W7DzXq)%_#E*#Z#$9JL7f;pK0ha_!nZ-M zbZ)?X!BR(>c<7M|pySpXJYf_23VXv8_L$UCQ`n>y@&F;>vo;hg#syTs1rz%zO+*Qg z#4~>jJq3KwfyWNd8=8CQRU`NjbQhjV+A86_unFQhm}Iq)y-kLAQ^cgE@SJzWSrhsV zVk|DHOJtV|kjCsM_-Xd;<>IdOdo25VdNVa9 zSzDToA$VbCMOtFf|{qeW@RylB=g+<7zya+)MDjWDS6nORsQcS4cmyTJ?Cl zchqIn5A26Vqgu%OK+>&(Ui}rWORa`=^9E$rf^`SR$P}`uY4M338;t(g=F^Z_xv)mq z738#|Abc)Gq8hr+&T8Cz7bU+*E%{)F86-dZxoJ97xwLYOeS1NkX+{l$;HnnW9~N8*4A3gv2NMc%rSk zHQHQSleB@dVoE@o{;K##@pX7ncOVjRJ6V=>F|H=UhtGipt1a;uh#4pz2E>6~h)7z4 zK1QUG{yC^F{fp5KMgJUB6aSDCK{LaVxeD{I5cHn=b5sK)OSf@FeQhUj5%A~rbk^3d z*m&Vj%h)yQziqBBUyT@75P4^Gb$R_}X8K5p`n_il9C-G?bw56H=ErA*4TZtHM4}qW z(?9098!!ES>%wzC=9lJg#Pbh4%eLSL&iweVs}qU5U?Htv${+Cq*ydk=f8-AM74Op> z(cOWrrFOz41?Fb-AY>&iVwz@jB`ofXg;ZUNxEb1KuMMgm+*-w zL4+gs;d164O84;_0L@yfm4Xz&3-s1fwAS>N{qK)fu#!0x3H}za8f9 zAd3dyA#f<06n$IwkvsL6j9)rCwr8M8@(6aYb7uwNtWojgMdHqq=KT4u;UTDw=t1l2 zXV0!j(PP0(4pH-IF&1`+V+-GG0zXiLW`lQ)G00Ole_KuEiTWT^x z@HWZzMNgep9|pj$zK(SsK5>oPW->R9snu**+UJb;?Z%>xdi9%icC#=+zc_!FVo~aZ zmaErIcXl*4ulxHsq5AK4agji zF+Q6D1rW$7%|22pq6S%CIMyk71d61Up}mT3@qs)@1R+jm)bY3<&(1O7A~$H3V@wg& z$!EQnE?@C$G~oCpt|tPG!5ZOls)Bex{M)nf#0{Tw8Qd!xohF2z2+)2=qRk}PCO4QY zoBFXolYhO%>M)8v57>AyT)-G`#R1^eSZH(`^O7!H7vV>M=ux0IBUg$ael|ilWnZ$R zZLqCCg@OOlEfh>=R(Hm>xkv2H7HX-|;2M`P5|7=r)_)rg%yn z1_zXZzNPsceKOnL=|JY+a4_I?A^Qck@|ZWeG#sn;CMp2}o2pvW6m}Me1Laj)+BX;F zTO_~3ZqS$QZLcsXYld!YSCr)1#b)Hya+w|AUOUrBnJ8c;C}Qz_SN9(<2u7PH5Q-;K z-lD4`;O&6fYgK%Qmi3;p>mS+Qd1pW7cV0MFR=eWb7+~5#GpZ+7w?^_RuvQh_DE67t z;yaL+ck1rcy$%iHZy9!WWO3nP#aKu%3`IqMJ&`kwm|+6s=$s-GQPXl^r%9KLVJ48# zkn>_5G@@36BUn)q_FUWN%#Sz%UQ4Fl;$U`5S;F8b^tlSd=AO+!r_T#k1b}O3uov0@ z{B72MQ#J$D-e%r>Fakd|pA#R{%n zFW6SLSiRuRkR#0xWr&^K3~X#*okJqUpVKDG2F12+IwEZ6+laV|GRTz@T`-Zun8bslcgNeF$4ol%IpdNk*d&nAZ-! z-QW&`d;2SQ;nwZf1jEfuFit8IexFRb;Ca=+8%3oKM*nhO%t?KpG(g>3bu-!S#zbq`!%RVIM z#%=n7f?t^(3`vsyJyL2V$7#5q!H)DkY^nuZUx^t?8W_Hl)HfuY65Pgghia(%eDn6JZvXA6(Z5}$ z-h$XmPif^J^J7PizBvbzWOo+w_TyCAHo{i*t!4JTh8 zxZ|I0-L_5r;1OR%LuvTg|M{8K2M($qyz#pF!3XaY7l0#YIxlj1kHUwq6tT_+b;po@ z@^Rhg!9k{SE+(3bzot>)>LfB{K+D1#NhYCs#Ji!rr1yi+0=^RGFe-794-N)KX_@+MP@Eh&F~-Q96a>LBI+l@m}FUj98H5S;cptByQA{pve51Gp=C6FGvXw z+r7-$TDC1g>RK!f3Q&w~s-LP;WcfF~LQj50+U1Rv|19G-TfGrS8UOlONx{IUs!xfF zi;9;lDYiud9{Cx;s+;(GqJmhpO0*<&{Y8*cH z_{IkpO-wYMUccd?Mf>KhRS}yb>h=hy6)TYVycWgcGI~P4(%YERl_0_|0>sKDg@Sbk zGYkL^d((lMI9{h>H0}0KKzEVLYsc4-$WqHQ1?k2mS1mEvJr-~U-xRw1uv#LI?1^i) zmIKAFEK`{mH2A~$z+(5!c-&Rha*^fs>$*0Sq&na3OcfWW)@?6p_FLWX7csiU>1ugPt&w=G^>ydo5fU4N`95G<*VD*2v( z*QUrd&3)BfA;d=LNRj=aXmb<2g9SNEIT_@$xzL~6hW^d|CZV>Bw^$KfxRcqv8z`s}pA6)Wg)n;r^asDWu1fZ1Dv zy%sEZ8} zZg{{QffA#x#+yKzH&SYl6@z%g8mfl$mNb#@JbEc@A6DXCKV2d5s78j3$Px<`Km1He z(c3~t$Kycr!oCI}9iWZ!bI-UV=9J6*+Kj^^*&HyWDF(BARJhORMQ)4&OF({y>5o~0 ze*3qL`6geUP5tM9x5ADzo_Gq7d~Q&mWy>pz{Y6fD9^%PO5qD?L!Q~lb-N1yIe_7O* zmPrV4=}vx5=<7ilb6u(H@K6hZ*fQ zwl!6)ZSW)zOK1x!5>vlnS`s3f`T z{V@YD!4UEoCJ94eFJrs4Sn^5)A%o=PzNT_pz+?D}C+*&f4uc(Ay->2OwW33qpEgP^ zt2yknTJwtREgcm#zH~U_OfkW}qRJ7ASVJzaK@pmSX4zsxY6nAY%=sYOx|OLvb%gy+ z#R2;=R*^|bBAOhiAm9dTr$?Hq4e(|#<$IIWk$TohsHO%JvT4v>ch7}a#C_1Wq>x`o zN0~iqnlyC)F=3n`QVOuB`MjWgB8B(?{73)%6iUSB6D@lgOc!J=eg%pMio-e0m~Tz` zx{f^sUk$i2lHTrIYLJco=FU!=p?KLTvo94f3J7jWH?1iQB;%(%9!YRRAL)DEUX&EY z=HI&AdXvLcsp|bMaY__d%9hH=%4^hjl2K*6s=F%O{<&ZC_&ph|{TcLC{~aFEn{|Ca)wor6A66d6>!R&Q(L_Zqregk9 z)?p!$VUG)=T!4kECJ9xLDGB%J6+N5T*Z9C&bBAur?!h8qdsYM<9L*3J5cZCQP4(N zcY(ltkjm3~0bb)S>x>ntlMORKWq^6W=CmWqMdJPnI?T5`_~>la4IuZB4jsvf2euP9 z45VnT5%W&62QDBU7^m<|gAJGx#xD`T_ea4V*g#@PXN6G$bnp_G8FVq7gS&LQpmG5l zh648F^rFrn5=ufZ{!lCcJUb*PLLdV= zkQ{8>D1T@R$WrF(yLuxwKLcht%*s~AjmBjO$hWV9j>iBW>kLoG) z4`gdIa2vxaWFfCb?;r3-CNgcpZ--oiv< z_Z`e@ZrWxx8XWKYTuqR5NUe0$F=7MmOj z!5(F~70iSAb~sVkj3;_dv96x=V@RAZIvFi3jShMRQGNSV&k3Uql>KJ2lVpVY^vcE( zf6HLo3a1ni#C+M_j9&)w+_$=KXFEnWjJp#Ri{Mu!+stM@g-W9tkGH04XlR}K-=d|S zxMu`B+CnV00*g-s)E3k8^8ZPvOMMK$IdlR7=mgzxb9CZ|pRO zfBF&s{3rTkGYfa~Y8RhHXN&Ew!iC%E7n02BxIvv2Z&GK~3+n8}_RT_rVMb&=*kOdb zaT=_EQ7OElk3j*>qhH#1{*6(keh6rv6m>Vjc9%rF z5lpr2+6F(26P$@^707Xdqqsq}5Fum)Y>qsL1l>v1pwuL_SB_JL*o0Lkzz8B9+jJuQWvzfN+XU57;ID zqH+K|z*EKPcQJ2oV+w{;yLr?oC^3s(I&1R4uFsT3Tg*noy_Ui}=NZr}pIf;WDglJ1r)I=@o|?-Gv}p!zdK#i>=Ot!p^*Z&ic=O&#ydA0o>AydGW~py+MYY9;U{> z|07H>Tac0?GHkU=(qAD9iD&1=xK%*%1B-jbJ)X=;t(NaF(XYhxz6ujT>ewt}WquTw>+wa38i#+Jo^x zK@n-5(gp#p5BJcH0Nz-3QUB0xLE8z88p0onx`WYBfhhN{jnpV#mrR1``mV-ZAy}J* zUKf0MLr8gbEa6C1o8+wlyS}(AzE{09QH5yZaG)>HYX%fmaS!4}1q7fw#cw#;+%4Bi zQbnX`M^*Ypi#2}>GNzb=kqCsu6~1_WL|fT6c6E)$Yu47*t`*OugVFGo zvgGC)^N{WOKG=0OURPIRM#Ni@Vzc}pCMdOb#bGnvm>+DrDwCg9p)E0+icD|7p)}vT zVO0wxddVW(2Mw!t@5)$bL!{2#`mn#`s00{U^&dTcCkux|rDauC{i@2URqUtq+v}cN zTZ5mcanixZ?Lp{Z=#K%*s4#adAS+R4?pQ>Y>6dcHdYzMz1(E(FotahSj%Acz2b#n5 zMwArbs&*_Owxd3GEb6u)qmfpwN1bVz16nciJ@O?F~(aBMVIRgRPYyC-9{e*E8;ahcJs0?`m@CXqZ9Dpu^~ zMkKZ|GYhj~G25AgIhhMkO&;cD7*iHtc`OJQi7?N~lh0yE>QVs6sX~@yDY(WJv0_#N zCz>*rfx!ok%&dx4vl>+s{Yq6`~;kKDw&8}g{8pXDG{@{pN&%1V#nBVb~7A8Zeh2w zlk7HjJG%ogJ$JIZ*xl?NcADMG?qm0}2Xr>}AbW_NVGkpj%~|#+dyG8}WXyByFW8gp zQ|u}BH2XAr1{?Tu>@)0n_5%AX`yBf`dy##C{Uv*eeUbeY`;zW|vzOT`>{a#}`)l*` zWOgF6Cp*$ToR!D3W0?NAqOsxOu>&^kGCv+1?YHvN>CrvBKu>4#)Wq2Mz#;SGcsAQN zHj1+d3#U^D2B&bBgVb7iI>F&+;jExN~RPMu5 zd&UkfT<)6~?CI&jD^3m!W{3N%lf4t!>}Y1H8)Z|@+}Y$v_r%orz}P5$QoG)logA7P z8<)n1yGOHz(XlE18-|hT$*Jz%fvhw#h9<}(-TkB4DHFa1dou^RNBfKeW78A;!`+jU zrv8cUzUl5MtOTilU~F>A-rqfv&44tZ*~7!x3H_eIiL4=eD9fu`v!na7!(-#w4E`Ur z<7|wd;U&>>IXjx2=s#r4j*LxBjtmZG^?ierz2@HOiOI2v%*fdOtiBh`Hsjs<2B$K; z-4lIAyyeK?(9#FqrC%T6Y_y>yup;bKifNnGa7*20eO7-@ZsUC zGCnalil4-Hb}~CUWk3%c8peaf@xf7Le6V+FdLk>1bdSp@$4XZQ(CnV>eq;aeSWh<7 zJv=4xBWceB+H1mi_hFi#80{S%(eJ@nTeIC0yt5~%+<5U!PeUj7O>`gVp3wJ=^-h}l z&_%t}g_FjMhf3dI_sH02pV&J+uJn$LjH9y+y=YJdlSbhOgQHqOcC^ogg5K$21Pn|l zJ<~K1CXGE4gV{a3-IG}(%^Tj~)}HRkq3l#GAQgi7ET!;Nfg$&-C!HHl@t`9FLps9#$p? zruXa_&Kf5Nx+kNnG+Q`5*G z4L{9a{qPw2aCodAogv|Ca8yr&hmWDGegLaX-#;plPJDhcC=M#hd1DM3T$f*?#XZL)hq;KrNXs!;%X;jt**WBAZ zf+;gLfgh4_oSo>#Pi1?Dq~2k4EqzXAZTOt-%g_^Oye-=4^f;Y}n0JPS)hA+g>h}zH z_siNemIg3ar}TrMV&YKt5IS{q2p!%#WTX#1k&HwV2KNktT3tNKbocc+E}cza74OfQ z#=FO{FnWgv$IaRyCp8v4i4%*OI2 zL(DTyhs|_0Hk=tDnvARc*(n1E$utO0)xtrwDOWPKXOE=KnUU=1v}0_XFSN}5?8MX{ z2205mn6wd~-pU=)V3;o(DuZ@BJuZ)T9~$o-cH}7Y;3(b@Gi_vn7E^zLK#eX`Kojwj z*~zH|%HBUXH89l5vhE)gJX30H!a_G?b#r>ft3nJ8;U3lys(BR2^c(8M! z@)(vcALWHjVsd=#Q93dSMywJHl6x`%2$~5aJittA@bl*Fvzn8whFK*#x Kj_>Q2Zu>vsTUp2e literal 0 HcmV?d00001 diff --git a/popup/fonts/Simple-Line-Icons.woff b/popup/fonts/Simple-Line-Icons.woff new file mode 100755 index 0000000000000000000000000000000000000000..b17d69491bf374d36e07339bedd3349caaa30d65 GIT binary patch literal 81332 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 literal 0 HcmV?d00001 diff --git a/popup/fonts/Simple-Line-Icons.woff2 b/popup/fonts/Simple-Line-Icons.woff2 new file mode 100755 index 0000000000000000000000000000000000000000..c49fccf510eb41b4aac2bfa21b3b87c254083bf8 GIT binary patch literal 30064 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

`i;W%DyRrxz&7=UY}f`DBwGN z^QCTGvyO~ey9-qw{@>K>*0ETVw6;Tilw(vetE!&HNSMvhCH4F|*L8W}J3TzIc5B!6 zX)TxLl{nFlFOSxB;2j)0hQZWfaCN|UD9kj_4@^De_#GQExLdpTyP#guE1OrO0fIS| zxSjBT(4D`0xgb6tBvdU<@K5*iOZQ*gSnDtyG3`*QWVP4SM&u7H&+jH&BW}m8EyH$^ zUXgJZm*E1jHtHi|Q5+XpdAO{^QB6mi?5b-JhnrSmxJ4K7BtY^5#({pvti`&@%0$Q2 zKS#P!{jl~ina%4b51z(3Ogng;jb=%q_{C)j33V+fF3{na1B>a)ssN0$vnHYY`KUED zSe%EkO*W05q|)u^)KjR<0`U9WGWZDyF78Jp?^|C;Z}dM?>i0LH*9cr>jv)Z zSG1v}2sOq?`&!|ERTez5|9SB5@8{1*IPxf3lJPZBH8|{t4*+#hIC2(Sb7kNbm z6L5j(={Se#lB)h)Z?$zT0;E07Zx+*lLpRocoYh0AW*YZ5e6RhG{FC6)EM*2&gS+2P=%z&>^(%zh`5z zgLD>8h=AJsbh+L}Rk}(EwwcJU$b;mCJY+Qk$h!y0R{kui%>TS2Y!d*nLks~4SNQ9) z_@oNH+XOzHqpn7mu?M>aX&%m)WOJ+5=CV`a#nsBSbb@Y4ZbER5|JF=n1T$aEvFF`% z8-85%3PC9P)l8!7DVWL9QIgqfw=i(zIglaV5Vzget2Hg3NE%x zMG{$n|nzfja!?p&3@OfNyE-nV9cbL$uVF_@@Ds2+#l8y|A%B{HUFYqllW(p zR@x8$6r9+@rTr0m3^WYA`nTXc-kTv?Ib*YdF^i)3iLZ!|?oauLC$Dw!FFrCq4)8^! z9f<$tq_#6A88-l*U5|6*8w z|2^F2HHfH+?GtdU=N<(1>)-kQxf;lef=?{q8^q}c;{U10NISx~>9|oRe%wJ6~J1Z65;d z=(Y!*>j%xC0;v553fqq6@+UKCkStbAzXT54{~JJX=;}#UR82R`|FxQ>ko#b@nkxiFP5uzZGB^NYkOyRZ~x%%==cN-fx_SjBnpke;_w6_ ziA2iC#K0ki|gkS{4 zU~zZ?kwm6YX>!>rAn>Q>huPq$!xLO><*_Z{#x>QeSTHQ zl@QmFh+!S&U=+V!GMCTwn2mY6+$U=%%-sDF*c0X0e{(tzN`gYJNgs*bmrdIOXr4D} zL+k<;Ra#hX@kL!cmQr3VX=8Y1Dc@Thj|+n^Y>o&HmsA0=J3^=QLAY~~=&_f~k5Jn; z)R&f{^GO-Qrm_z@tg1?WN4MB2?2;`*r_J6)?_SAa9Bi^N)jHilp^?UtyL*(Xw_6gN zVq2BM3^CmV1HNeMalw9r%K?`w`9_Qsb;9USryfMcm>WM(=LR ztbq{KsYn=gAjPWkGZ&b#+wVo}E6h;)z$*mnxA(jC`u}J29x*FqJ18o$4nFC} zvFgb!_ikRB;I!nhReeHgKO~2Rw(*{2fs?7--oNWX2~#Hr$OAGp&S@3{;Q|p$vsyJV z63qCm2it-^B&N~VshPmr?_?bhuVLSeQpw?@b@a+~P_2Usvtwz$OWH0x3p-3czA_cDba!pl*@^f+grlr^GBA&$-&697h8;)xLr3_o3DzV5aT_ z)%|p2>$8rdh~Cov;d(KTCq9$cBdU$K&(YSEG3Fq#7~Y#U=(*ZPH#jZytJaZyWV5Nb zuBy?y!`!)UdeWz5z6T3xW^!Sl+)C4KqsMGKhgKis1v8jlct;GKn~r_NUn5qCiaS|t zlpiO^fiDsu_NYOKPbvJX(^=L}s<sX%kAfVQ|%HgqUl`-14}f>Sf7oy0?Y+D3*=!i31@-J)gq_dxNCf6tVT XVaD<2gWrVGOVQy$mL_vR2><{9vXVmp literal 0 HcmV?d00001 diff --git a/popup/partials/tags.html b/popup/partials/tags.html index 18f1c8d..d824023 100644 --- a/popup/partials/tags.html +++ b/popup/partials/tags.html @@ -1,5 +1,11 @@

-
+
diff --git a/src/background/ShazamService.js b/src/background/ShazamService.js index 49d7e2a..1e234f9 100644 --- a/src/background/ShazamService.js +++ b/src/background/ShazamService.js @@ -18,6 +18,11 @@ Logger.info('[Shazam] Injecting content script to get "inid" from Local Storage...'); + chrome.tabs.insertCSS(tabId, { + file: 'contentscripts/shazam.css', + runAt: 'document_start' + }); + chrome.tabs.executeScript(tabId, { file: 'contentscripts/shazamLocalStorage.js', runAt: 'document_end' diff --git a/src/background/SpotifyService.js b/src/background/SpotifyService.js index 660ae71..3c89c28 100644 --- a/src/background/SpotifyService.js +++ b/src/background/SpotifyService.js @@ -475,9 +475,14 @@ callback(new Error('Please authorize Shazify to access your Spotify account.')); } }); + // Too many requests, wait and try again + } else if(jqXHR.status === 429) { + setTimeout(function() { + Spotify.call(options, callback); + }, 1000); } else { - callback(new Error('Error calling API')); - Logger.error('[Spotify] Error calling API : '+textStatus+'.'); + callback(new Error('Error calling API ('+ jqXHR.status +').')); + Logger.error('[Spotify] Error calling API ('+ jqXHR.status +') : '+textStatus+'.'); } }); }); diff --git a/src/contentscripts/shazam.css b/src/contentscripts/shazam.css new file mode 100644 index 0000000..c79cafd --- /dev/null +++ b/src/contentscripts/shazam.css @@ -0,0 +1,45 @@ +/* The Modal (background) */ +.shazify-modal { + position: fixed; /* Stay in place */ + z-index: 2000; /* Sit on top */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + background-color: rgb(0,0,0); /* Fallback color */ + background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ +} + +/* Modal Content/Box */ +.shazify-modal-content { + position:relative; + background-color: #89C161; + margin: 15% auto; /* 15% from the top and centered */ + padding: 30px 25px; + width: 80%; /* Could be more or less, depending on screen size */ +} + +.shazify-modal-content p { + color:white; + letter-spacing: 0.5px; +} + +/* The Close Button */ +.shazify-modal-close { + color: #000000; + position: absolute; + right:15px; + top:15px; + height:15px; + line-height:15px; + font-weight:300; + font-size: 28px; +} + +.shazify-modal-close:hover, +.shazify-modal-close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} \ No newline at end of file diff --git a/src/contentscripts/shazamLocalStorage.js b/src/contentscripts/shazamLocalStorage.js index 854e129..73473fb 100644 --- a/src/contentscripts/shazamLocalStorage.js +++ b/src/contentscripts/shazamLocalStorage.js @@ -23,6 +23,43 @@ function retrieveWindowVariables(variables) { return ret; } +function openModal(text) { + var modalContainer = document.createElement('div'); + modalContainer.className = 'shazify-modal'; + + var modalContent = document.createElement('div'); + modalContent.className = 'shazify-modal-content'; + modalContainer.appendChild(modalContent); + + var modalClose = document.createElement('span'); + modalClose.className = 'shazify-modal-close'; + modalClose.innerHTML = '×'; + + var modalText = document.createElement('p'); + modalText.innerHTML = text; + + modalContent.appendChild(modalClose); + modalContent.appendChild(modalText); + + var closeModal = function() { + modalContainer.remove(); + }; + + modalClose.onclick = closeModal; + modalContainer.onclick = closeModal; + + document.body.appendChild(modalContainer); +} + +var modalOpened = false; +function openModalOnlyOnce(text) { + if(modalOpened) { + return; + } + + return openModal(text); +} + function getAndSendLocalStorage() { var pageVars = retrieveWindowVariables(['localStorage']); @@ -37,6 +74,8 @@ function getAndSendLocalStorage() { observeForLoginAndGetLocalStorage(); } else { console.log('inid seems fine! Congrats! You are now logged in on Shazam.'); + + openModalOnlyOnce('Shazify: Login is successful! Please open Shazify again.'); } }); diff --git a/src/popup/css/popup.css b/src/popup/css/popup.css index dac5f07..6210735 100644 --- a/src/popup/css/popup.css +++ b/src/popup/css/popup.css @@ -317,6 +317,70 @@ https://github.com/yui/pure/blob/master/LICENSE.md overflow:hidden; } + .topbar .simplelineicon { + width:16px; + height:16px; + font-size:16px; + line-height: 16px; + float:left; + margin-left:7px; + margin-top:6px; + } + + .topbar .dropdown { + position:absolute; + top:44px; + background-color: white; + border:1px solid #cccccc; + padding:10px; + z-index: 2000; + right: 30px; + box-sizing: content-box; + height: 16px; + } + + .topbar .dropdown:after, .topbar .dropdown:before { + bottom: 100%; + left: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + } + + .topbar .dropdown:after { + border-color: rgba(255, 255, 255, 0); + border-bottom-color: #ffffff; + border-width: 6px; + margin-left: -6px; + } + .topbar .dropdown:before { + border-color: rgba(204, 204, 204, 0); + border-bottom-color: #cccccc; + border-width: 7px; + margin-left: -7px; + } + + .topbar .dropdown .filter { + font-size:16px; + line-height: 16px; + height: 16px; + opacity: 0.3; + margin-right:5px; + cursor:pointer; + display:inline-block; + } + + .topbar .dropdown .filter:last-child { + margin-right: 0; + } + + .topbar .dropdown .filter.selected { + opacity:1; + } + .topbar .iconmelon { width:16px; height:16px; @@ -535,6 +599,7 @@ https://github.com/yui/pure/blob/master/LICENSE.md .tag .tag-image { width:36px; + border-radius: 50%; float:left; margin-right:10px; } diff --git a/src/popup/css/simple-line-icons.css b/src/popup/css/simple-line-icons.css new file mode 100755 index 0000000..890ae56 --- /dev/null +++ b/src/popup/css/simple-line-icons.css @@ -0,0 +1,778 @@ +@font-face { + font-family: 'simple-line-icons'; + src: url('fonts/Simple-Line-Icons.eot?v=2.4.0'); + src: url('fonts/Simple-Line-Icons.eot?v=2.4.0#iefix') format('embedded-opentype'), url('fonts/Simple-Line-Icons.woff2?v=2.4.0') format('woff2'), url('fonts/Simple-Line-Icons.ttf?v=2.4.0') format('truetype'), url('fonts/Simple-Line-Icons.woff?v=2.4.0') format('woff'), url('fonts/Simple-Line-Icons.svg?v=2.4.0#simple-line-icons') format('svg'); + font-weight: normal; + font-style: normal; +} +/* + Use the following CSS code if you want to have a class per icon. + Instead of a list of all class selectors, you can use the generic [class*="icon-"] selector, but it's slower: +*/ +.icon-user, +.icon-people, +.icon-user-female, +.icon-user-follow, +.icon-user-following, +.icon-user-unfollow, +.icon-login, +.icon-logout, +.icon-emotsmile, +.icon-phone, +.icon-call-end, +.icon-call-in, +.icon-call-out, +.icon-map, +.icon-location-pin, +.icon-direction, +.icon-directions, +.icon-compass, +.icon-layers, +.icon-menu, +.icon-list, +.icon-options-vertical, +.icon-options, +.icon-arrow-down, +.icon-arrow-left, +.icon-arrow-right, +.icon-arrow-up, +.icon-arrow-up-circle, +.icon-arrow-left-circle, +.icon-arrow-right-circle, +.icon-arrow-down-circle, +.icon-check, +.icon-clock, +.icon-plus, +.icon-minus, +.icon-close, +.icon-event, +.icon-exclamation, +.icon-organization, +.icon-trophy, +.icon-screen-smartphone, +.icon-screen-desktop, +.icon-plane, +.icon-notebook, +.icon-mustache, +.icon-mouse, +.icon-magnet, +.icon-energy, +.icon-disc, +.icon-cursor, +.icon-cursor-move, +.icon-crop, +.icon-chemistry, +.icon-speedometer, +.icon-shield, +.icon-screen-tablet, +.icon-magic-wand, +.icon-hourglass, +.icon-graduation, +.icon-ghost, +.icon-game-controller, +.icon-fire, +.icon-eyeglass, +.icon-envelope-open, +.icon-envelope-letter, +.icon-bell, +.icon-badge, +.icon-anchor, +.icon-wallet, +.icon-vector, +.icon-speech, +.icon-puzzle, +.icon-printer, +.icon-present, +.icon-playlist, +.icon-pin, +.icon-picture, +.icon-handbag, +.icon-globe-alt, +.icon-globe, +.icon-folder-alt, +.icon-folder, +.icon-film, +.icon-feed, +.icon-drop, +.icon-drawer, +.icon-docs, +.icon-doc, +.icon-diamond, +.icon-cup, +.icon-calculator, +.icon-bubbles, +.icon-briefcase, +.icon-book-open, +.icon-basket-loaded, +.icon-basket, +.icon-bag, +.icon-action-undo, +.icon-action-redo, +.icon-wrench, +.icon-umbrella, +.icon-trash, +.icon-tag, +.icon-support, +.icon-frame, +.icon-size-fullscreen, +.icon-size-actual, +.icon-shuffle, +.icon-share-alt, +.icon-share, +.icon-rocket, +.icon-question, +.icon-pie-chart, +.icon-pencil, +.icon-note, +.icon-loop, +.icon-home, +.icon-grid, +.icon-graph, +.icon-microphone, +.icon-music-tone-alt, +.icon-music-tone, +.icon-earphones-alt, +.icon-earphones, +.icon-equalizer, +.icon-like, +.icon-dislike, +.icon-control-start, +.icon-control-rewind, +.icon-control-play, +.icon-control-pause, +.icon-control-forward, +.icon-control-end, +.icon-volume-1, +.icon-volume-2, +.icon-volume-off, +.icon-calendar, +.icon-bulb, +.icon-chart, +.icon-ban, +.icon-bubble, +.icon-camrecorder, +.icon-camera, +.icon-cloud-download, +.icon-cloud-upload, +.icon-envelope, +.icon-eye, +.icon-flag, +.icon-heart, +.icon-info, +.icon-key, +.icon-link, +.icon-lock, +.icon-lock-open, +.icon-magnifier, +.icon-magnifier-add, +.icon-magnifier-remove, +.icon-paper-clip, +.icon-paper-plane, +.icon-power, +.icon-refresh, +.icon-reload, +.icon-settings, +.icon-star, +.icon-symbol-female, +.icon-symbol-male, +.icon-target, +.icon-credit-card, +.icon-paypal, +.icon-social-tumblr, +.icon-social-twitter, +.icon-social-facebook, +.icon-social-instagram, +.icon-social-linkedin, +.icon-social-pinterest, +.icon-social-github, +.icon-social-google, +.icon-social-reddit, +.icon-social-skype, +.icon-social-dribbble, +.icon-social-behance, +.icon-social-foursqare, +.icon-social-soundcloud, +.icon-social-spotify, +.icon-social-stumbleupon, +.icon-social-youtube, +.icon-social-dropbox, +.icon-social-vkontakte, +.icon-social-steam { + font-family: 'simple-line-icons'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.icon-user:before { + content: "\e005"; +} +.icon-people:before { + content: "\e001"; +} +.icon-user-female:before { + content: "\e000"; +} +.icon-user-follow:before { + content: "\e002"; +} +.icon-user-following:before { + content: "\e003"; +} +.icon-user-unfollow:before { + content: "\e004"; +} +.icon-login:before { + content: "\e066"; +} +.icon-logout:before { + content: "\e065"; +} +.icon-emotsmile:before { + content: "\e021"; +} +.icon-phone:before { + content: "\e600"; +} +.icon-call-end:before { + content: "\e048"; +} +.icon-call-in:before { + content: "\e047"; +} +.icon-call-out:before { + content: "\e046"; +} +.icon-map:before { + content: "\e033"; +} +.icon-location-pin:before { + content: "\e096"; +} +.icon-direction:before { + content: "\e042"; +} +.icon-directions:before { + content: "\e041"; +} +.icon-compass:before { + content: "\e045"; +} +.icon-layers:before { + content: "\e034"; +} +.icon-menu:before { + content: "\e601"; +} +.icon-list:before { + content: "\e067"; +} +.icon-options-vertical:before { + content: "\e602"; +} +.icon-options:before { + content: "\e603"; +} +.icon-arrow-down:before { + content: "\e604"; +} +.icon-arrow-left:before { + content: "\e605"; +} +.icon-arrow-right:before { + content: "\e606"; +} +.icon-arrow-up:before { + content: "\e607"; +} +.icon-arrow-up-circle:before { + content: "\e078"; +} +.icon-arrow-left-circle:before { + content: "\e07a"; +} +.icon-arrow-right-circle:before { + content: "\e079"; +} +.icon-arrow-down-circle:before { + content: "\e07b"; +} +.icon-check:before { + content: "\e080"; +} +.icon-clock:before { + content: "\e081"; +} +.icon-plus:before { + content: "\e095"; +} +.icon-minus:before { + content: "\e615"; +} +.icon-close:before { + content: "\e082"; +} +.icon-event:before { + content: "\e619"; +} +.icon-exclamation:before { + content: "\e617"; +} +.icon-organization:before { + content: "\e616"; +} +.icon-trophy:before { + content: "\e006"; +} +.icon-screen-smartphone:before { + content: "\e010"; +} +.icon-screen-desktop:before { + content: "\e011"; +} +.icon-plane:before { + content: "\e012"; +} +.icon-notebook:before { + content: "\e013"; +} +.icon-mustache:before { + content: "\e014"; +} +.icon-mouse:before { + content: "\e015"; +} +.icon-magnet:before { + content: "\e016"; +} +.icon-energy:before { + content: "\e020"; +} +.icon-disc:before { + content: "\e022"; +} +.icon-cursor:before { + content: "\e06e"; +} +.icon-cursor-move:before { + content: "\e023"; +} +.icon-crop:before { + content: "\e024"; +} +.icon-chemistry:before { + content: "\e026"; +} +.icon-speedometer:before { + content: "\e007"; +} +.icon-shield:before { + content: "\e00e"; +} +.icon-screen-tablet:before { + content: "\e00f"; +} +.icon-magic-wand:before { + content: "\e017"; +} +.icon-hourglass:before { + content: "\e018"; +} +.icon-graduation:before { + content: "\e019"; +} +.icon-ghost:before { + content: "\e01a"; +} +.icon-game-controller:before { + content: "\e01b"; +} +.icon-fire:before { + content: "\e01c"; +} +.icon-eyeglass:before { + content: "\e01d"; +} +.icon-envelope-open:before { + content: "\e01e"; +} +.icon-envelope-letter:before { + content: "\e01f"; +} +.icon-bell:before { + content: "\e027"; +} +.icon-badge:before { + content: "\e028"; +} +.icon-anchor:before { + content: "\e029"; +} +.icon-wallet:before { + content: "\e02a"; +} +.icon-vector:before { + content: "\e02b"; +} +.icon-speech:before { + content: "\e02c"; +} +.icon-puzzle:before { + content: "\e02d"; +} +.icon-printer:before { + content: "\e02e"; +} +.icon-present:before { + content: "\e02f"; +} +.icon-playlist:before { + content: "\e030"; +} +.icon-pin:before { + content: "\e031"; +} +.icon-picture:before { + content: "\e032"; +} +.icon-handbag:before { + content: "\e035"; +} +.icon-globe-alt:before { + content: "\e036"; +} +.icon-globe:before { + content: "\e037"; +} +.icon-folder-alt:before { + content: "\e039"; +} +.icon-folder:before { + content: "\e089"; +} +.icon-film:before { + content: "\e03a"; +} +.icon-feed:before { + content: "\e03b"; +} +.icon-drop:before { + content: "\e03e"; +} +.icon-drawer:before { + content: "\e03f"; +} +.icon-docs:before { + content: "\e040"; +} +.icon-doc:before { + content: "\e085"; +} +.icon-diamond:before { + content: "\e043"; +} +.icon-cup:before { + content: "\e044"; +} +.icon-calculator:before { + content: "\e049"; +} +.icon-bubbles:before { + content: "\e04a"; +} +.icon-briefcase:before { + content: "\e04b"; +} +.icon-book-open:before { + content: "\e04c"; +} +.icon-basket-loaded:before { + content: "\e04d"; +} +.icon-basket:before { + content: "\e04e"; +} +.icon-bag:before { + content: "\e04f"; +} +.icon-action-undo:before { + content: "\e050"; +} +.icon-action-redo:before { + content: "\e051"; +} +.icon-wrench:before { + content: "\e052"; +} +.icon-umbrella:before { + content: "\e053"; +} +.icon-trash:before { + content: "\e054"; +} +.icon-tag:before { + content: "\e055"; +} +.icon-support:before { + content: "\e056"; +} +.icon-frame:before { + content: "\e038"; +} +.icon-size-fullscreen:before { + content: "\e057"; +} +.icon-size-actual:before { + content: "\e058"; +} +.icon-shuffle:before { + content: "\e059"; +} +.icon-share-alt:before { + content: "\e05a"; +} +.icon-share:before { + content: "\e05b"; +} +.icon-rocket:before { + content: "\e05c"; +} +.icon-question:before { + content: "\e05d"; +} +.icon-pie-chart:before { + content: "\e05e"; +} +.icon-pencil:before { + content: "\e05f"; +} +.icon-note:before { + content: "\e060"; +} +.icon-loop:before { + content: "\e064"; +} +.icon-home:before { + content: "\e069"; +} +.icon-grid:before { + content: "\e06a"; +} +.icon-graph:before { + content: "\e06b"; +} +.icon-microphone:before { + content: "\e063"; +} +.icon-music-tone-alt:before { + content: "\e061"; +} +.icon-music-tone:before { + content: "\e062"; +} +.icon-earphones-alt:before { + content: "\e03c"; +} +.icon-earphones:before { + content: "\e03d"; +} +.icon-equalizer:before { + content: "\e06c"; +} +.icon-like:before { + content: "\e068"; +} +.icon-dislike:before { + content: "\e06d"; +} +.icon-control-start:before { + content: "\e06f"; +} +.icon-control-rewind:before { + content: "\e070"; +} +.icon-control-play:before { + content: "\e071"; +} +.icon-control-pause:before { + content: "\e072"; +} +.icon-control-forward:before { + content: "\e073"; +} +.icon-control-end:before { + content: "\e074"; +} +.icon-volume-1:before { + content: "\e09f"; +} +.icon-volume-2:before { + content: "\e0a0"; +} +.icon-volume-off:before { + content: "\e0a1"; +} +.icon-calendar:before { + content: "\e075"; +} +.icon-bulb:before { + content: "\e076"; +} +.icon-chart:before { + content: "\e077"; +} +.icon-ban:before { + content: "\e07c"; +} +.icon-bubble:before { + content: "\e07d"; +} +.icon-camrecorder:before { + content: "\e07e"; +} +.icon-camera:before { + content: "\e07f"; +} +.icon-cloud-download:before { + content: "\e083"; +} +.icon-cloud-upload:before { + content: "\e084"; +} +.icon-envelope:before { + content: "\e086"; +} +.icon-eye:before { + content: "\e087"; +} +.icon-flag:before { + content: "\e088"; +} +.icon-heart:before { + content: "\e08a"; +} +.icon-info:before { + content: "\e08b"; +} +.icon-key:before { + content: "\e08c"; +} +.icon-link:before { + content: "\e08d"; +} +.icon-lock:before { + content: "\e08e"; +} +.icon-lock-open:before { + content: "\e08f"; +} +.icon-magnifier:before { + content: "\e090"; +} +.icon-magnifier-add:before { + content: "\e091"; +} +.icon-magnifier-remove:before { + content: "\e092"; +} +.icon-paper-clip:before { + content: "\e093"; +} +.icon-paper-plane:before { + content: "\e094"; +} +.icon-power:before { + content: "\e097"; +} +.icon-refresh:before { + content: "\e098"; +} +.icon-reload:before { + content: "\e099"; +} +.icon-settings:before { + content: "\e09a"; +} +.icon-star:before { + content: "\e09b"; +} +.icon-symbol-female:before { + content: "\e09c"; +} +.icon-symbol-male:before { + content: "\e09d"; +} +.icon-target:before { + content: "\e09e"; +} +.icon-credit-card:before { + content: "\e025"; +} +.icon-paypal:before { + content: "\e608"; +} +.icon-social-tumblr:before { + content: "\e00a"; +} +.icon-social-twitter:before { + content: "\e009"; +} +.icon-social-facebook:before { + content: "\e00b"; +} +.icon-social-instagram:before { + content: "\e609"; +} +.icon-social-linkedin:before { + content: "\e60a"; +} +.icon-social-pinterest:before { + content: "\e60b"; +} +.icon-social-github:before { + content: "\e60c"; +} +.icon-social-google:before { + content: "\e60d"; +} +.icon-social-reddit:before { + content: "\e60e"; +} +.icon-social-skype:before { + content: "\e60f"; +} +.icon-social-dribbble:before { + content: "\e00d"; +} +.icon-social-behance:before { + content: "\e610"; +} +.icon-social-foursqare:before { + content: "\e611"; +} +.icon-social-soundcloud:before { + content: "\e612"; +} +.icon-social-spotify:before { + content: "\e613"; +} +.icon-social-stumbleupon:before { + content: "\e614"; +} +.icon-social-youtube:before { + content: "\e008"; +} +.icon-social-dropbox:before { + content: "\e00c"; +} +.icon-social-vkontakte:before { + content: "\e618"; +} +.icon-social-steam:before { + content: "\e620"; +} diff --git a/src/popup/js/controllers/TagsCtrl.js b/src/popup/js/controllers/TagsCtrl.js index d9644f5..574901e 100644 --- a/src/popup/js/controllers/TagsCtrl.js +++ b/src/popup/js/controllers/TagsCtrl.js @@ -5,6 +5,42 @@ angular.module('Shazify').controller('TagsCtrl', function($scope, $location, $in $scope.updating = function() { return TagsService.updating(); }; $scope.tags = function() { return TagsService.list; }; + $scope.shouldShowFilters = false; + $scope.toggleShowFilters = function() { + $scope.shouldShowFilters = !$scope.shouldShowFilters; + }; + + // Status : 1 = just added, 2 = not found in spotify, 3 = found, 4 = added to playlist + $scope.statusFilters = [ + { + icon: 'icon-check', + status: 4 + }, + { + icon: 'icon-close', + status: 2 + }, + { + icon: 'icon-clock', + status: 1 + } + ]; + + $scope.tagsStatusFilters = [1, 2, 3, 4]; + + $scope.filterByStatus = function(tag) { + return ($scope.tagsStatusFilters.indexOf(tag.status) !== -1); + }; + + $scope.toggleStatusFilter = function(status) { + var i = $scope.tagsStatusFilters.indexOf(status); + if(i === -1) { + $scope.tagsStatusFilters.push(status); + } else { + delete $scope.tagsStatusFilters[i]; + } + }; + $scope.newSearch = { show: false, tag: null, From f218032f5c89af8300377e14014aa23a290534f1 Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Sun, 11 Dec 2016 22:16:16 +0100 Subject: [PATCH 09/15] [WIP] Store tags in IndexedDB instead of localStorage --- README.md | 1 + manifest.json | 2 +- package.json | 2 +- src/background/DbService.js | 10 +-- src/background/TagsService.js | 109 ++++++++++++++++----------- src/background/UpdateService.js | 47 ++++++++---- src/background/background.js | 11 ++- src/popup/js/services/TagsService.js | 15 +++- 8 files changed, 124 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 3b4ac6b..d166a89 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ grunt bundle - Create an update page to let the users know of the new functionnalities - Add an edit button for tracks found, to le the user know he can change the track found - Replace SVG icons with SimpleLineIcons +- Use bulk* methods from Dexie.js to speedup tags addition/update to DB ## Disclaimer diff --git a/manifest.json b/manifest.json index f199ca9..c4bbf20 100755 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAopSL/q/2d97dHxCQ1w26B4EONSEo4CZZYFqu3MlJKA5ifSXkyoNSlkG6afDuNoXmiTWFCAawRgGwuO3yi0QvfeMJpz+M9nXnt8PQsVP10Mb7o3SHIihxIXRMFAJvggvHW4NCuDznJixpo2ixEeCL8LGkS0ZL93MgYtEOuaGPdcBTiAUya3Nr3mdkn+nTHldQvthassiDYZystDTuKwkf0vTZkDCyWC8bfI7xZTxQ6IFcS2WQgUm0ix5hgv+qyOQzOwh26R6BQmZTFj7YiSqhgZsZRebhMcn+hxd8kSegS1ZAcKv/oS63Ej5/IAB5NijrPXR3zivQZ6oIJjr+xMOU9wIDAQAB", "name": "Shazify", - "version": "0.3.2", + "version": "0.4.0", "manifest_version": 2, "description": "__MSG_appDesc__", "homepage_url": "http://www.leeroy.me", diff --git a/package.json b/package.json index 9459da5..90d46c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chrome-shazify", - "version": "0.3.2", + "version": "0.4.0", "author": "Leeroy Brun ", "private": true, "description": "Easily sync your Shazam tracks with a Spotify playlist", diff --git a/src/background/DbService.js b/src/background/DbService.js index a36379b..5d51557 100644 --- a/src/background/DbService.js +++ b/src/background/DbService.js @@ -2,14 +2,14 @@ var Db = (function() { var db = new Dexie('Shazify'); - /*db.version(1).stores({ - users: "++id, name, &username, *email, address.city", - relations: "++, userId1, userId2, [userId1+userId2], relation" + db.version(1).stores({ + // shazamId is primary key & unique + tags: 'shazamId, date, spotifyId, status', // name, artist, query, image }); db.open().catch(function (e) { - console.error("Open failed: " + e.stack); - });*/ + console.error("Open failed: " + e.stack); + }); return db; }()); diff --git a/src/background/TagsService.js b/src/background/TagsService.js index 7939911..0740109 100644 --- a/src/background/TagsService.js +++ b/src/background/TagsService.js @@ -1,6 +1,5 @@ -(function(StorageHelper, Logger, Spotify, Shazam){ +(function(Database, StorageHelper, Logger, Spotify, Shazam){ var Tags = { - list: [], lastUpdate: new Date(0), // Add/update a tag in the tags list @@ -34,27 +33,16 @@ // Private : called from Tags.add, add the specified tag to list or update it _addToList: function(tag, callback) { - // TODO: use Array.prototype.find when available on Chrome - var found = false; - for(var i in Tags.list) { - if(Tags.list[i].shazamId == tag.shazamId) { - found = true; - $.extend(Tags.list[i], tag); // Update existing tag - break; - } - } - - if(!found) { - Tags.list.push(tag); - } + Tags.db.put(tag).then(function() { + return Tags.db.get(tag.shazamId).then(function(tag) { + return callback(null, tag); + }); + }).catch(function(reason) { + Logger.error('[Tags] Error adding/updating tag to DB : '+ reason +'.'); + Logger.error(reason); - Tags.list.sort(function (a, b) { - if (a.date > b.date) { return -1; } - if (a.date < b.date) { return 1; } - return 0; + return callback(reason); }); - - callback(); }, _updateStatusNbTags: 0, // Nombre de tags au total en cours d'ajout @@ -104,61 +92,90 @@ updatePlaylist: function(callback) { Logger.info('[Tags] Updating playlist on Spotify.'); - var tracksToAdd = []; // Used to revert "status" if an error occurs - var tracksIdsToAdd = []; // Used to add tracks to playlist + // Used to revert "status" if an error occurs + Tags.db.where('status').equals(3).toArray().then(function(tracksToAdd) { + var tracksIdsToAdd = []; // Used to add tracks to playlist - for(var i in Tags.list) { - var tag = Tags.list[i]; - - if(tag.status == 3) { - tracksToAdd.push(tag); - tracksIdsToAdd.push(tag.spotifyId); - tag.status = 4; + for(var i in tracksToAdd) { + tracksIdsToAdd.push(tracksToAdd[i].spotifyId); + tracksToAdd[i].status = 4; // Set status as added, will not be saved to DB if an error occurs } - } - Spotify.playlist.addTracks(tracksIdsToAdd, function(err) { - if(err) { - Logger.info('[Tags] Cannot add tags to playlist, reverting tags status.'); - // If an error occurs, revert tag status to 3. This will let the system retry addition later. - for(var i in tracksToAdd) { - tracksToAdd[i].status = 3; + Spotify.playlist.addTracks(tracksIdsToAdd, function(err) { + if(err) { + Logger.info('[Tags] Cannot add tags to playlist.'); + } else { + // Only update DB if addition succeeded + Tags.db.bulkPut(tracksToAdd).then(function() { + Logger.info('[Tags] Updated in DB.'); + }).catch(function(reason) { + Logger.error('[Tags] Error updating tags in DB.'); + Logger.error(reason); + }); } - } - Tags.save(callback); + Tags.save(callback); + }); + }).catch(function(reason) { + Logger.error('[Tags] Error getting tags list to add.'); + Logger.error(reason); }); }, - // Save tags data (list & lastUpdate) + // Save tags data (lastUpdate) save: function(callback) { callback = callback || function(){}; Logger.info('[Tags] Saving tags data.'); - Tags.data.set({'tagsList': Tags.list, 'lastUpdate': Tags.lastUpdate.getTime()}, function() { + Tags.data.set({'lastUpdate': Tags.lastUpdate.getTime()}, function() { callback(); }); }, + + getList: function(callback) { + callback = callback || function(){}; + + Tags.db.orderBy('date').toArray().then(function(tagsList) { + return callback(null, tagsList); + }).catch(function(reason) { + return callback(reason); + }); + }, + + count: function(callback) { + Tags.db.count().then(function(tagsCount) { + return callback(null, tagsCount); + }).catch(function(reason) { + return callback(reason); + }); + }, // Load tags data (list & lastUpdate) load: function(callback) { callback = callback || function(){}; - Tags.data.get(['tagsList', 'lastUpdate'], function(items) { - Tags.list = items.tagsList || []; + Tags.data.get('lastUpdate', function(items) { Tags.lastUpdate = new Date(items.lastUpdate) || new Date(0); Tags.lastUpdate = (!isNaN(Tags.lastUpdate.valueOf())) ? Tags.lastUpdate : new Date(0); - Logger.info('[Tags] Got from storage -> tagsList: '+ Tags.list.length +' items.'); + Tags.count(function(error, tagsCount) { + if(error) { + return Logger.error('[Tags] Error counting tags in DB : '+ error +'.'); + } + + Logger.info('[Tags] '+ tagsCount +' items in DB.'); + }); + Logger.info('[Tags] Got from storage -> lastUpdate: '+ Tags.lastUpdate +'.'); callback(); }); }, - data: new StorageHelper('Tags') + data: new StorageHelper('Tags', 'sync'), + db: Database.tags }; window.s2s.Tags = Tags; -})(window.s2s.StorageHelper, window.s2s.Logger, window.s2s.Spotify, window.s2s.Shazam); \ No newline at end of file +})(window.s2s.Db, window.s2s.StorageHelper, window.s2s.Logger, window.s2s.Spotify, window.s2s.Shazam); \ No newline at end of file diff --git a/src/background/UpdateService.js b/src/background/UpdateService.js index 80c941c..4906d33 100644 --- a/src/background/UpdateService.js +++ b/src/background/UpdateService.js @@ -57,30 +57,51 @@ _updates: [ {'version': 20, 'perform': function(callback) { - s2s.Logger.info('[Update] Cleaning extension\'s background data.'); + s2s.Logger.info('[Update] Cleaning extension\'s background data.'); var popups = chrome.extension.getViews({type: 'popup'}); - if(popups && popups.length) { - popups[0].window.close(); - } + if(popups && popups.length) { + popups[0].window.close(); + } - ChromeHelper.clearStorage(); + ChromeHelper.clearStorage(); - // Clear cached data from background script - Tags.data.clearCache(); - Spotify.data.clearCache(); + // Clear cached data from background script + Tags.data.clearCache(); + Spotify.data.clearCache(); - // Reload tags, will reset list & lastUpdate - s2s.Tags.load(); + // Reload tags, will reset list & lastUpdate + s2s.Tags.load(); - UpdateService.openUpdatePage('0.2.0'); + UpdateService.openUpdatePage('0.2.0'); - callback(); + callback(); }}, {'version': 23, 'perform': function(callback) { UpdateService.openUpdatePage('0.2.3'); - callback(); + callback(); + }}, + {'version': 40, 'perform': function(callback) { + s2s.Logger.info('[Update] Cleaning extension\'s background data.'); + + var popups = chrome.extension.getViews({type: 'popup'}); + if(popups && popups.length) { + popups[0].window.close(); + } + + ChromeHelper.clearStorage(); + + // Clear cached data from background script + Tags.data.clearCache(); + Spotify.data.clearCache(); + + // Reload tags, will reset list & lastUpdate + s2s.Tags.load(); + + UpdateService.openUpdatePage('0.4.0'); + + callback(); }} ] }; diff --git a/src/background/background.js b/src/background/background.js index a418243..22c5136 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -5,7 +5,7 @@ $(document).ready(function() { s2s.Logger.info('[init] Loading tags from storage...'); s2s.Tags.load(function() { - s2s.Logger.info('[init] '+ s2s.Tags.list.length +' tags loaded.'); + }); s2s.CanvasIcon.load(); @@ -86,8 +86,13 @@ $(document).ready(function() { s2s.Spotify.data.clearCache(); s2s.CanvasIcon.stopRotation(); - // Reload tags, will reset list & lastUpdate - s2s.Tags.load(); + s2s.Tags.db.clear().then(function() { + // Reload tags, will reset list & lastUpdate + s2s.Tags.load(); + }).catch(function(reason) { + s2s.Logger.error('[core] Cannot clear tags database.'); + s2s.Logger.error(reason); + }); } }); diff --git a/src/popup/js/services/TagsService.js b/src/popup/js/services/TagsService.js index 737832d..a7c77cd 100644 --- a/src/popup/js/services/TagsService.js +++ b/src/popup/js/services/TagsService.js @@ -2,9 +2,9 @@ angular.module('Shazify').factory('TagsService', function($timeout, $interval, B // Tags list : http://stackoverflow.com/a/18569690/1160800 var TagsService = { - list: BackgroundService.Tags.list, + list: [], updateListInterval: null, - updating: function() { return BackgroundService.updating; }, + updating: function() { return true; }, // true until first list fetch is complete getUpdateStatus: function(callback) { $timeout(function() { @@ -16,8 +16,9 @@ angular.module('Shazify').factory('TagsService', function($timeout, $interval, B // We define an interval to update the list while tags' updating is in progress if(TagsService.updateListInterval === null) { TagsService.updateListInterval = $interval(function() { - // The intervall will automatically trigger a scope update, so we don't need to redefine the list - //TagsService.list = BackgroundService.Tags.list; + BackgroundService.Tags.getList(function(error, tagsList) { + TagsService.list = tagsList; + }); if(TagsService.updating() === false) { $interval.cancel(TagsService.updateListInterval); @@ -55,6 +56,12 @@ angular.module('Shazify').factory('TagsService', function($timeout, $interval, B }); } }; + + BackgroundService.Tags.getList(function(error, tagsList) { + TagsService.list = tagsList; + + TagsService.updating = function() { return BackgroundService.updating; }; + }); return TagsService; }); \ No newline at end of file From 35119765a615148b0b439f7b88c286d554e6ccfb Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Mon, 12 Dec 2016 12:05:33 +0100 Subject: [PATCH 10/15] Replace icons with Simple Line Icons. Add pagination for tags. Change the way we update/fetch tags from popup, much more like an API and not with a simple reference. --- README.md | 6 +- _locales/en/messages.json | 4 ++ _locales/fr/messages.json | 4 ++ popup/partials/tags.html | 95 ++++++++++--------------- src/background/TagsService.js | 39 ++++++++++- src/background/UpdateService.js | 11 ++- src/popup/css/popup.css | 101 +++++++++++++++++++++++++-- src/popup/js/controllers/TagsCtrl.js | 61 +++++++++++++--- src/popup/js/services/TagsService.js | 36 ++++++---- 9 files changed, 266 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index d166a89..0cde6c6 100644 --- a/README.md +++ b/README.md @@ -62,12 +62,12 @@ grunt bundle - Add tracks in order on Spotify - Reorder existing tracks - Store tags and Spotify tracks found in IndexedDB (use Dexie.js) -- Remove tags from localStorage +- [x] Remove tags from localStorage - When select a different Spotify track for a tag, remove the old one from playlist (if not associated with another tag) and add the new one - Create an update page to let the users know of the new functionnalities - Add an edit button for tracks found, to le the user know he can change the track found -- Replace SVG icons with SimpleLineIcons -- Use bulk* methods from Dexie.js to speedup tags addition/update to DB +- [x] Replace SVG icons with SimpleLineIcons +- Use bulk* methods from Dexie.js to speedup tags addition/update to DB or transactions ## Disclaimer diff --git a/_locales/en/messages.json b/_locales/en/messages.json index cd5d90e..8f597c4 100755 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -61,6 +61,10 @@ "message": "Updating tags...", "description": "" }, + "loadingTags": { + "message": "Loading...", + "description": "" + }, "doNotCloseBrowser": { "message": "Please do not close your browser", "description": "" diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index f464328..de8403d 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -61,6 +61,10 @@ "message": "Mise à jour des tags en cours...", "description": "" }, + "loadingTags": { + "message": "Chargement...", + "description": "" + }, "doNotCloseBrowser": { "message": "Veuillez ne pas fermer votre navigateur", "description": "" diff --git a/popup/partials/tags.html b/popup/partials/tags.html index d824023..9065bb5 100644 --- a/popup/partials/tags.html +++ b/popup/partials/tags.html @@ -6,27 +6,14 @@ - - - - - - + + - - - - - - - + + - - - - - - + +

myTags

@@ -34,55 +21,42 @@

myTags

-
- - - - - - +
+
-

updatingTags
{{updateStatus}}

+

updatingTags
{{updateStatus}}

doNotCloseBrowser

+
+
+
+ +
+

loadingTags

+
+
-

- - - - - +

+

noTagsFound

-
- - - - - +
+
-
- - - - - +
+
@@ -91,6 +65,17 @@

myTags

{{tag.name}}
{{tag.artist}}
+ -

myTags

+

myTags ({{filteredCount}}/{{totalCount}})

@@ -36,13 +36,13 @@

myTags

loadingTags

-
+

noTagsFound

-
+
@@ -65,17 +65,33 @@

myTags

{{tag.name}}
{{tag.artist}}
-
+
+
+ +
+
{{tag.name}}
@@ -109,13 +114,18 @@

searchResults


searchResultsPleaseSelect

-
+
+
+
+ +
+
{{track.name}}
diff --git a/src/background/SpotifyService.js b/src/background/SpotifyService.js index 3c89c28..cffa972 100644 --- a/src/background/SpotifyService.js +++ b/src/background/SpotifyService.js @@ -300,6 +300,24 @@ } }, + // Get details for a track from it's ID + getTrack: function(trackId, callback) { + Logger.info('[Spotify] Getting details for track "'+ trackId +'"...'); + + Spotify.call({ + endpoint: '/v1/tracks/'+ trackId, + method: 'GET' + }, function(err, data) { + if(err) { + Logger.info('[Spotify] Error getting track "'+ trackId +'".'); + Logger.error(err); + return callback(err); + } + + return callback(null, data); + }); + }, + // Find a track on Spotify findTrack: function(query, trackName, artist, callback) { Logger.info('[Spotify] Searching for track "'+ query +'"...'); @@ -375,7 +393,8 @@ name: track.name, artist: artist, image: track.album.images[track.album.images.length-1].url, - id: track.id + id: track.id, + previewUrl: track.preview_url }); } diff --git a/src/background/TagsService.js b/src/background/TagsService.js index 28a7944..b185f42 100644 --- a/src/background/TagsService.js +++ b/src/background/TagsService.js @@ -2,7 +2,7 @@ var Tags = { lastUpdate: new Date(0), - // Add/update a tag in the tags list + // Add/update a tag in the tags DB and search for Spotify track add: function(tag, callback) { callback = callback || function(){}; @@ -20,8 +20,7 @@ tag.status = 2; } else { tag.status = 3; - tag.image = track.album.images[track.album.images.length-1].url; - tag.spotifyId = track.id; + tag = Tags.setSpotifyInfosToTag(tag, track); } Tags._addToList(tag, callback); @@ -45,6 +44,14 @@ }); }, + setSpotifyInfosToTag: function(tag, track) { + tag.image = track.album.images[track.album.images.length-1].url; + tag.previewUrl = track.preview_url; + tag.spotifyId = track.id; + + return tag; + }, + _updateStatusNbTags: 0, // Nombre de tags au total en cours d'ajout _updateStatusTagsAdded: 0, // Nombre de tags déjà ajoutés @@ -146,8 +153,8 @@ promise.then(function() { return Tags.db.where('spotifyId').equals(oldSpotifyId).count().then(function(count) { Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : '+ count +' other tags have the old one too.'); - // No other tags linked to the old Spotify track - if(count === 0) { + // No other tags linked to the old Spotify track except this one + if(count <= 1) { return new Promise(function(resolve, reject) { Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : no other tag is linked to the old track, removing from playlist.'); Spotify.playlist.removeTracks([oldSpotifyId], function(err) { @@ -175,12 +182,26 @@ return resolve(); }); }); - // -- Update tag in DB with new spotifyId -- + // -- Getting track details from Spotify -- }).then(function() { + Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : getting track details from Spotify.'); + return new Promise(function(resolve, reject) { + Spotify.getTrack(newSpotifyId, function(err, track) { + if(err) { + return reject(err); + } + + return resolve(track); + }); + }); + // -- Update tag in DB with new spotifyId -- + }).then(function(track) { Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : updating tag in DB.'); - tag.spotifyId = newSpotifyId; + tag = Tags.setSpotifyInfosToTag(tag, track); tag.status = 4; - // TODO: update artist, name, etc. as in the .add() method + + console.log(tag); + return Tags.db.put(tag); }); }).then(function() { diff --git a/src/popup/css/popup.css b/src/popup/css/popup.css index 1fb2716..1d58778 100644 --- a/src/popup/css/popup.css +++ b/src/popup/css/popup.css @@ -145,6 +145,94 @@ https://github.com/yui/pure/blob/master/LICENSE.md font-size: 125%; } +/* Audio player */ + .audio-player { + position: relative; + text-align: center; + } + + .audio-player button { + cursor: pointer; + position:relative; + display: block; + margin: 0 auto; + padding: 0; + width: 24px; + height: 24px; + background: transparent; + color: #2ECC71; /*1ECD97*/ + border: 2px solid #2ECC71; + border-radius: 50%; + } + + .audio-player svg.progress-circle { + position: absolute; + top: 0; + left: 50%; + -webkit-transform: translateX(-50%); + transform: translateX(-50%); + pointer-events: none; + } + + .audio-player svg.progress-circle path { + opacity: 0; + fill: none; + } + + .audio-player svg.progress-circle path { + stroke: #2ECC71; + stroke-width: 5; + } + + .audio-player.playing button { + border-color: #ddd; + } + + .audio-player button span { + position:absolute; + top: 2px; + left: 3px; + } + + .audio-player button svg { + color:#2ECC71; + fill:#2ECC71; + stroke: #2ECC71; + } + + .audio-player span.play { + opacity:1; + } + + .audio-player span.pause { + opacity:0; + top: 3px; + left: 3px; + } + + .audio-player.playing span.play { + -webkit-transition: opacity 0.15s; + transition: opacity 0.15s; + opacity:0; + } + + .audio-player.playing span.pause { + -webkit-transition: opacity 0.15s; + transition: opacity 0.15s; + opacity:1; + } + + .audio-player button span { + -webkit-transition: opacity 0.3s 0.1s; + transition: opacity 0.3s 0.1s; + } + + .audio-player.playing svg.progress-circle path { + opacity: 1; + -webkit-transition: stroke-dashoffset 0.3s; + transition: stroke-dashoffset 0.3s; + } + /* Animations */ @-webkit-keyframes spin { @@ -648,12 +736,35 @@ https://github.com/yui/pure/blob/master/LICENSE.md animation-duration:0; } + .tag:hover .tag-actions { + display:block; + } + + .tag-actions { + display:none; + float:right; + margin-left:10px; + margin-top:2px; + } + + .tag-actions .player { + margin-top: 3px; + } + + .tag-status.hidden+.tag-actions { + margin-right:8px; + } + .tag-status { float:right; margin-left:10px; margin-top:2px; } + .tag-status.hidden { + display:none; + } + .tag-status .iconmelon { width:32px; height:32px; diff --git a/src/popup/js/controllers/TagsCtrl.js b/src/popup/js/controllers/TagsCtrl.js index 4cb4ae4..58dc832 100644 --- a/src/popup/js/controllers/TagsCtrl.js +++ b/src/popup/js/controllers/TagsCtrl.js @@ -147,6 +147,9 @@ angular.module('Shazify').controller('TagsCtrl', function($scope, $location, $in track: '' }, selectedTrack: null, + isSelectedTrack: function(track) { + return this.selectedTrack && track.id == this.selectedTrack.id; + }, send: function() { SpotifyService.genQuery($scope.newSearch.query.track, $scope.newSearch.query.artist, function(query) { diff --git a/src/popup/js/directives/audioPlayer.js b/src/popup/js/directives/audioPlayer.js index 158adae..f13b0c8 100644 --- a/src/popup/js/directives/audioPlayer.js +++ b/src/popup/js/directives/audioPlayer.js @@ -1,8 +1,47 @@ angular.module('Shazify').directive('audioPlayer', function(AudioService) { return { - restrict: 'E', - link: function($scope, elem) { - - } - }; + restrict: 'E', + scope: { + audioSrc: '=' + }, + templateUrl: 'partials/audio-player.html', + link: function(scope, element, attrs) { + var svgProgress = element[0].getElementsByClassName('progress-circle')[0]; + + scope.svgCircleLength = svgProgress.querySelectorAll('path')[0].getTotalLength(); + scope.strokeDashoffset = 0; + + scope.progress = 0; + scope.playing = false; + + scope.togglePlay = function() { + if(scope.playing) { + scope.pause(); + } else { + scope.play(); + } + }; + + scope.play = function() { + AudioService.setOnProgress(scope.onProgress); + AudioService.play(scope.audioSrc); + scope.playing = true; + AudioService.setOnEnd(scope.onEnd); + }; + + scope.onProgress = function(progress) { + scope.progress = progress; + scope.strokeDashoffset = scope.svgCircleLength * (1 - progress); + }; + + scope.onEnd = function(progress) { + scope.playing = false; + }; + + scope.pause = function() { + scope.playing = false; + AudioService.pause(scope.audioSrc); + }; + } + }; }); \ No newline at end of file diff --git a/src/popup/js/services/AudioService.js b/src/popup/js/services/AudioService.js index 9049782..7c6ab8d 100644 --- a/src/popup/js/services/AudioService.js +++ b/src/popup/js/services/AudioService.js @@ -6,14 +6,73 @@ angular.module('Shazify').factory('AudioService', function($timeout) { AudioService._audio.src = src; }, + setOnProgress: function(onProgress) { + AudioService.onProgress = onProgress; + }, + + callOnProgress: function(progress) { + if(typeof AudioService.onProgress === 'function') { + $timeout(function() { + AudioService.onProgress(progress); + }, 0); + } + }, + + setOnEnd: function(onEnd) { + AudioService.onEnd = onEnd; + }, + + callOnEnd: function() { + if(typeof AudioService.onEnd === 'function') { + var onEnd = AudioService.onEnd; + $timeout(function() { + onEnd(); + }, 0); + } + }, + + onEnd: null, + onProgress: null, + progressTimer: null, + + _onEndedSet: false, + play: function(src) { src = src || null; + AudioService.callOnEnd(); + if(src) { AudioService.setSrc(src); } AudioService._audio.play(); + + if(!AudioService._onEndedSet) { + AudioService._audio.addEventListener('ended', function() { + AudioService.callOnEnd(); + AudioService.clearOnProgress(); + }); + + AudioService._onEndedSet = true; + } + + AudioService.progressTimer = setInterval(function() { + if(AudioService._audio.currentTime && AudioService._audio.duration) { + AudioService.callOnProgress(AudioService._audio.currentTime / AudioService._audio.duration); + } + }, 500); + }, + + pause: function() { + AudioService.callOnEnd(); + AudioService._audio.pause(); + AudioService.clearOnProgress(); + }, + + clearOnProgress: function() { + clearInterval(AudioService.progressTimer); + AudioService.progressTimer = null; } }; From 43dd6aeb47255aedc9c901f263f1f391c90ee8d0 Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Tue, 13 Dec 2016 22:35:09 +0100 Subject: [PATCH 13/15] List all changes on v0.4.0 ROADMAP --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fd87229..d6cdd16 100644 --- a/README.md +++ b/README.md @@ -58,16 +58,24 @@ grunt bundle ## Roadmap for v0.4.0 +- Do not remove from localStorage and load it all again, get tags from localStorage and add them in DB to keep spotifyId +- Create an update page to let the users know of the new functionnalities +- Set a track to "not found" (status = 5? manual search, will not search automatically again for these tags) +- (Use bulk* methods from Dexie.js to speedup tags addition/update to DB or transactions) +- [x] Handle new Shazam login & API +- [x] Match track name in lower case +- [x] Filter functionnality to show only tags found/not found +- [x] New icons +- [x] Pagination +- [x] Handle "Too Many Requests" error on Spotify +- [x] Update AngularJS to 1.5.9 +- [x] Manually select the track wanted from Spotify search results - [x] Custom update scripts, handle versions like 0.2.10 - [x] Store tags and Spotify tracks found in IndexedDB (use Dexie.js) - [x] Remove tags from localStorage -- Do not remove from localStorage and load it all again, get tags from localStorage and add them in DB to keep spotifyId -- When select a different Spotify track for a tag, remove the old one from playlist (if not associated with another tag) and add the new one -- Create an update page to let the users know of the new functionnalities +- [x] When select a different Spotify track for a tag, remove the old one from playlist (if not associated with another tag) and add the new one - [x] Replace SVG icons with SimpleLineIcons -- Set a track to "not found" -- Add audio preview ("preview_url" from Spotify API) -- Use bulk* methods from Dexie.js to speedup tags addition/update to DB or transactions +- [x] Add audio preview ("preview_url" from Spotify API) ## Disclaimer From 3be097944dea7d525375214df7a23614f54ce5cb Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Tue, 13 Dec 2016 22:36:57 +0100 Subject: [PATCH 14/15] Add ROADMAP for v1.0 --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index d6cdd16..9ee7fa4 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,11 @@ grunt bundle - [x] Replace SVG icons with SimpleLineIcons - [x] Add audio preview ("preview_url" from Spotify API) +## Roadmap for v1.0 + +- Refactor. Code is messy at some places... +- Use React/Inferno/Vue.js/Angular 2.x instead of Angular 1.x + ## Disclaimer Shazify is not affiliated with Shazam Entertainment Limited. From b060ae6d0181720bc4a3b3f8b42c2465e1d05372 Mon Sep 17 00:00:00 2001 From: Leeroy Brun Date: Wed, 14 Dec 2016 14:08:25 +0100 Subject: [PATCH 15/15] Update to v0.4.0 don't remove but move tags to IndexedDB & update them from Spotify. Set track as not found. Display app updates in UI. Better handling of 429 errors for Spotify. Bugfixes. Add Changelor. Add update pages for v0.4.0. Handle versions like 0.10.23 in update script. Refactoring. Default volume for audio preview = 50%. --- CHANGELOG.md | 17 + README.md | 29 +- _locales/en/messages.json | 12 + _locales/fr/messages.json | 12 + popup/partials/tags.html | 31 +- src/background/SpotifyService.js | 25 +- src/background/TagsService.js | 179 +++- src/background/UpdateService.js | 105 +- src/background/background.js | 19 +- src/popup/css/popup.css | 31 +- src/popup/js/controllers/TagsCtrl.js | 67 +- src/popup/js/services/AudioService.js | 22 +- src/popup/js/services/TagsService.js | 31 +- static/fonts/Simple-Line-Icons.eot | Bin 0 -> 54266 bytes static/fonts/Simple-Line-Icons.svg | 200 ++++ static/fonts/Simple-Line-Icons.ttf | Bin 0 -> 54056 bytes static/fonts/Simple-Line-Icons.woff | Bin 0 -> 81332 bytes static/fonts/Simple-Line-Icons.woff2 | Bin 0 -> 30064 bytes static/img/icon128.png | Bin 5084 -> 4353 bytes static/simple-line-icons.css | 1346 +++++++++++++++++++++++++ static/style.css | 58 +- static/update-0.4.0-en.html | 29 + static/update-0.4.0-fr.html | 29 + 23 files changed, 2102 insertions(+), 140 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 static/fonts/Simple-Line-Icons.eot create mode 100644 static/fonts/Simple-Line-Icons.svg create mode 100644 static/fonts/Simple-Line-Icons.ttf create mode 100644 static/fonts/Simple-Line-Icons.woff create mode 100644 static/fonts/Simple-Line-Icons.woff2 create mode 100644 static/simple-line-icons.css create mode 100644 static/update-0.4.0-en.html create mode 100644 static/update-0.4.0-fr.html diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..53bca18 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +## v0.4.0 + +- [x] Store tags in IndexedDB instead of Local Storage +- [x] Pagination +- [x] Filter tags by status +- [x] Handle new Shazam login & API +- [x] Manually select the track we want from Spotify +- [x] Audio preview +- [x] Set a track as "not found" if none of the results is corresponding +- [x] New icons +- [x] Handle "Too Many Requests" error on Spotify +- [x] Update AngularJS to 1.5.9 +- [x] Update scripts handle versions like 0.2.10 (two diggits / part) +- [x] Do not match a Spotify track if the title is not the same (lower case matching) +- [x] Bugfixes \ No newline at end of file diff --git a/README.md b/README.md index 9ee7fa4..40a4091 100644 --- a/README.md +++ b/README.md @@ -56,31 +56,14 @@ grunt build grunt bundle ``` -## Roadmap for v0.4.0 - -- Do not remove from localStorage and load it all again, get tags from localStorage and add them in DB to keep spotifyId -- Create an update page to let the users know of the new functionnalities -- Set a track to "not found" (status = 5? manual search, will not search automatically again for these tags) -- (Use bulk* methods from Dexie.js to speedup tags addition/update to DB or transactions) -- [x] Handle new Shazam login & API -- [x] Match track name in lower case -- [x] Filter functionnality to show only tags found/not found -- [x] New icons -- [x] Pagination -- [x] Handle "Too Many Requests" error on Spotify -- [x] Update AngularJS to 1.5.9 -- [x] Manually select the track wanted from Spotify search results -- [x] Custom update scripts, handle versions like 0.2.10 -- [x] Store tags and Spotify tracks found in IndexedDB (use Dexie.js) -- [x] Remove tags from localStorage -- [x] When select a different Spotify track for a tag, remove the old one from playlist (if not associated with another tag) and add the new one -- [x] Replace SVG icons with SimpleLineIcons -- [x] Add audio preview ("preview_url" from Spotify API) - ## Roadmap for v1.0 -- Refactor. Code is messy at some places... -- Use React/Inferno/Vue.js/Angular 2.x instead of Angular 1.x +- [ ] Refactor. Code is messy at some places... +- [ ] Use React/Inferno/Vue.js/Angular 2.x instead of Angular 1.x +- [ ] Use Promises instead of callbacks +- [ ] Use bulk* methods from Dexie.js to speedup tags addition/update to DB or transactions +- [ ] Put "new search" (in TagsCtrl) in a separate directive/controller +- [ ] Add volume control for audio preview ## Disclaimer diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 5126646..987a828 100755 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -65,6 +65,10 @@ "message": "Loading...", "description": "" }, + "updatingApp": { + "message": "Shazify is updating to latest version...", + "description": "" + }, "doNotCloseBrowser": { "message": "Please do not close your browser", "description": "" @@ -111,6 +115,14 @@ "message": "Please select the right track below", "description": "" }, + "notFound": { + "message": "Not found", + "description": "" + }, + "notFoundText": { + "message": "No track is matching.", + "description": "" + }, "saveSelectedTrack": { "message": "Save the selected track", "description": "" diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 0b2d7b2..5d4f1bf 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -65,6 +65,10 @@ "message": "Chargement...", "description": "" }, + "updatingApp": { + "message": "Shazify se met à jour à la dernière version...", + "description": "" + }, "doNotCloseBrowser": { "message": "Veuillez ne pas fermer votre navigateur", "description": "" @@ -111,6 +115,14 @@ "message": "Veuillez sélectionner le bon morceau ci-dessous.", "description": "" }, + "notFound": { + "message": "Non trouvé", + "description": "" + }, + "notFoundText": { + "message": "Aucun morceau ne correspond.", + "description": "" + }, "saveSelectedTrack": { "message": "Sauvegarder ce choix", "description": "" diff --git a/popup/partials/tags.html b/popup/partials/tags.html index 2a8f1a2..e6da118 100644 --- a/popup/partials/tags.html +++ b/popup/partials/tags.html @@ -4,7 +4,7 @@
@@ -19,7 +19,7 @@

myTags ({{filteredCount}}/{{totalCount}})

-
+
@@ -28,7 +28,7 @@

myTags ({{filteredCou

doNotCloseBrowser

-
+
@@ -36,6 +36,15 @@

myTags ({{filteredCou

loadingTags

+
+
+
+ +
+

updatingApp

+

doNotCloseBrowser

+
+

@@ -44,12 +53,12 @@

myTags ({{filteredCou

-
+
-
+
@@ -113,6 +122,18 @@

myTags ({{filteredCou

searchResults


searchResultsPleaseSelect

+ +
+
+
+ +
+
+
+ +
notFound
+
notFoundText
+
diff --git a/src/background/SpotifyService.js b/src/background/SpotifyService.js index cffa972..bf07495 100644 --- a/src/background/SpotifyService.js +++ b/src/background/SpotifyService.js @@ -496,6 +496,7 @@ }); // Too many requests, wait and try again } else if(jqXHR.status === 429) { + Logger.error('[Spotify] Too many requests, waiting 1s... ('+ jqXHR.status +') : '+textStatus+'.'); setTimeout(function() { Spotify.call(options, callback); }, 1000); @@ -564,8 +565,16 @@ }); }) .fail(function(jqXHR, textStatus) { - Logger.error('[Spotify] Error getting token : '+textStatus+'.'); - callback(false); + if(jqXHR.status === 429) { + Logger.error('[Spotify] Too many requests, waiting 1s... ('+ jqXHR.status +') : '+textStatus+'.'); + + setTimeout(function() { + Spotify.refreshToken(callback); + }, 1000); + } else { + Logger.error('[Spotify] Error getting token : '+textStatus+'.'); + callback(false); + } }); } else { Logger.info('[Spotify] No refresh token stored... open login.'); @@ -595,8 +604,16 @@ Spotify.saveAccessToken(data, callback); }) .fail(function(jqXHR, textStatus) { - Logger.error('[Spotify] Error getting access token : '+ textStatus +'.'); - callback(false); + if(jqXHR.status === 429) { + Logger.error('[Spotify] Too many requests, waiting 1s... ('+ jqXHR.status +') : '+textStatus+'.'); + + setTimeout(function() { + Spotify.getAccessToken(authCode, callback); + }, 1000); + } else { + Logger.error('[Spotify] Error getting access token : '+ textStatus +'.'); + callback(false); + } }); }, diff --git a/src/background/TagsService.js b/src/background/TagsService.js index b185f42..bd8dd59 100644 --- a/src/background/TagsService.js +++ b/src/background/TagsService.js @@ -7,7 +7,7 @@ callback = callback || function(){}; tag.spotifyId = tag.spotifyId || null; - tag.status = tag.status || 1; // Status : 1 = just added, 2 = not found in spotify, 3 = found, 4 = added to playlist + tag.status = tag.status || 1; // Status : 1 = just added, 2 = not found in spotify, 3 = found, 4 = added to playlist, 5 = not found, manual search tag.query = tag.query || Spotify.genQuery(tag.name, tag.artist); @@ -71,7 +71,7 @@ Shazam.getTags(Tags.lastUpdate, function(err, tags) { if(!err && Array.isArray(tags)) { - Logger.info('[Tags] Got '+ tags.length +' tags from Shazam.'); + Logger.info('[Tags] Got '+ tags.length +' new tags from Shazam.'); Tags._updateStatusNbTags = tags.length; @@ -129,7 +129,10 @@ }); }, + // Manually select a Spotify track for a tag selectSpotifyTrack: function(shazamId, newSpotifyId, callback) { + var Promise = Dexie.Promise; + Tags.db.get(shazamId).then(function(tag) { var oldSpotifyId = tag.spotifyId; @@ -141,8 +144,7 @@ return callback(); } - var Promise = Dexie.Promise; - var promise = Promise.resolve(); + var promise = Promise.resolve(tag); // -- Remove old track from playlist -- // Tag was already linked to a Spotify track, but we link it to a new one @@ -151,62 +153,63 @@ if(oldSpotifyId) { Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : an other track was already defined.'); promise.then(function() { - return Tags.db.where('spotifyId').equals(oldSpotifyId).count().then(function(count) { - Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : '+ count +' other tags have the old one too.'); - // No other tags linked to the old Spotify track except this one - if(count <= 1) { - return new Promise(function(resolve, reject) { - Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : no other tag is linked to the old track, removing from playlist.'); - Spotify.playlist.removeTracks([oldSpotifyId], function(err) { - if(err) { - return reject(err); - } - - return resolve(); - }); - }); - } + return new Promise(function(resolve, reject) { + Tags.removeTrackFromPlaylist(shazamId, oldSpotifyId, function(err) { + if(err) { + return reject(err); + } + + return resolve(tag); + }); }); }); } - // -- Add new track to playlist -- - return promise.then(function() { - return new Promise(function(resolve, reject) { - Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : adding new track to playlist.'); - Spotify.playlist.addTracks([newSpotifyId], function(err) { - if(err) { - return reject(err); - } + return promise; - return resolve(); - }); + // -- Add new track to playlist -- + }).then(function(tag) { + return new Promise(function(resolve, reject) { + Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : adding new track to playlist.'); + Spotify.playlist.addTracks([newSpotifyId], function(err) { + if(err) { + return reject(err); + } + + return resolve(tag); }); - // -- Getting track details from Spotify -- - }).then(function() { - Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : getting track details from Spotify.'); - return new Promise(function(resolve, reject) { - Spotify.getTrack(newSpotifyId, function(err, track) { - if(err) { - return reject(err); - } + }); + + // -- Getting track details from Spotify -- + }).then(function(tag) { + Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : getting track details from Spotify.'); + return new Promise(function(resolve, reject) { + Spotify.getTrack(newSpotifyId, function(err, track) { + if(err) { + return reject(err); + } - return resolve(track); + return resolve({ + tag: tag, + track: track }); }); - // -- Update tag in DB with new spotifyId -- - }).then(function(track) { - Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : updating tag in DB.'); - tag = Tags.setSpotifyInfosToTag(tag, track); - tag.status = 4; - - console.log(tag); - - return Tags.db.put(tag); }); + + // -- Update tag in DB with new spotifyId -- + }).then(function(result) { + Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : updating tag in DB.'); + result.tag = Tags.setSpotifyInfosToTag(result.tag, result.track); + result.tag.status = 4; + + return Tags.db.put(result.tag); + + // -- Finished -- }).then(function() { Logger.info('[Tags] Replacing Spotify track for '+ shazamId +' : all done!'); return callback(); + + // -- Catch all errors -- }).catch(function(reason) { Logger.error('[Tags] Error trying to replace Spotify track for '+ shazamId +'.'); Logger.error(reason); @@ -214,6 +217,86 @@ }); }, + // Remove track from playlist if not used by another tag + removeTrackFromPlaylist: function(shazamId, spotifyId, callback) { + Tags.db.where('spotifyId').equals(spotifyId).filter(function(tag) { + // Filter to get only tags != to the one for which we want to remove the track + return tag.shazamId !== shazamId; + }).count().then(function(count) { + Logger.info('[Tags] Removing Spotify track '+ spotifyId +' : '+ count +' other tags have the old one too.'); + // No other tags linked to the old Spotify track except this one + if(count === 0) { + Logger.info('[Tags] Removing Spotify track '+ spotifyId +' : no other tag is linked to the old track, removing from playlist.'); + + Spotify.playlist.removeTracks([spotifyId], function(err) { + if(err) { + return callback(err); + } + + return callback(); + }); + } else { + return callback(); + } + }); + }, + + // Set a tag as not found + // Will stop searching automatically for it + setAsNotFound: function(shazamId, callback) { + var Promise = Dexie.Promise; + + Tags.db.get(shazamId).then(function(tag) { + var oldSpotifyId = tag.spotifyId; + + Logger.info('[Tags] Setting '+ shazamId +' as not found.'); + + var promise = Promise.resolve(tag); + + // -- Remove old track from playlist -- + // Tag was already linked to a Spotify track, but we link it to a new one + // If other tags are linked to the old track -> don't change anything in playlist + // If no other is linked to it, remove it to playlist + if(tag.spotifyId) { + Logger.info('[Tags] Checking if we need to remove Spotify track for '+ shazamId +' from playlist.'); + promise.then(function(tag) { + return new Promise(function(resolve, reject) { + Tags.removeTrackFromPlaylist(shazamId, tag.spotifyId, function(err) { + if(err) { + return reject(err); + } + + return resolve(tag); + }); + }); + }); + } + + return promise; + + // -- Update tag in DB -- + }).then(function(tag) { + Logger.info('[Tags] Setting '+ shazamId +' as not found: updating tag in DB.'); + tag.image = null; + tag.previewUrl = null; + tag.spotifyId = null; + tag.status = 5; // Manual search + + return Tags.db.put(tag); + + // -- Finished -- + }).then(function() { + Logger.info('[Tags] Setting '+ shazamId +' as not found: all done!'); + return callback(); + + // -- Catch all errors -- + }).catch(function(reason) { + Logger.error('[Tags] Error trying to setting '+ shazamId +' as not found.'); + Logger.error(reason); + return callback(reason); + }); + }, + // Save tags data (lastUpdate) save: function(callback) { callback = callback || function(){}; @@ -231,7 +314,7 @@ var tags = Tags.db; - tags = tags.orderBy('date'); + tags = tags.orderBy('date').reverse(); // Count all tags before filtering them tags.count().then(function(totalCount) { diff --git a/src/background/UpdateService.js b/src/background/UpdateService.js index b6d5179..26a83cd 100644 --- a/src/background/UpdateService.js +++ b/src/background/UpdateService.js @@ -1,10 +1,34 @@ -(function(Logger, ChromeHelper, Spotify, Shazam, Tags){ +(function(StorageHelper, Logger, ChromeHelper, Spotify, Shazam, Tags){ var UpdateService = { update: function(initVersionTxt, finalVersionTxt) { - var rePoint = new RegExp('\\.', 'g'); + // Block tags update while updating extention + s2s.updatingApp = true; + + var initVersionParts = initVersionTxt.split('.'); + var finalVersionParts = finalVersionTxt.split('.'); + + initVersionTxt = ''; + finalVersionTxt = ''; + + // Split at each point and make sure each part has a length of 2 + initVersionParts.forEach(function(part) { + if(part.length < 2) { + part = '0'+ part; + } + + initVersionTxt += part; + }); + + finalVersionParts.forEach(function(part) { + if(part.length < 2) { + part = '0'+ part; + } - var initVersion = parseInt(initVersionTxt.replace(rePoint, '')); - var finalVersion = parseInt(finalVersionTxt.replace(rePoint, '')); + finalVersionTxt += part; + }); + + var initVersion = parseInt(initVersionTxt); + var finalVersion = parseInt(finalVersionTxt); var startIndex = null; var endIndex = null; @@ -40,9 +64,12 @@ }); }, function() { Logger.info('[Updater] All update scripts applied !'); + + s2s.updatingApp = false; }); } else { Logger.info('[Updater] No update script defined to go from v'+ initVersionTxt +' to v'+ finalVersionTxt +'.'); + s2s.updatingApp = false; } }, @@ -63,7 +90,7 @@ */ _updates: [ {'version': 20, 'perform': function(callback) { - s2s.Logger.info('[Update] Cleaning extension\'s background data.'); + Logger.info('[Update] Cleaning extension\'s background data.'); var popups = chrome.extension.getViews({type: 'popup'}); if(popups && popups.length) { @@ -77,7 +104,7 @@ Spotify.data.clearCache(); // Reload tags, will reset list & lastUpdate - s2s.Tags.load(); + Tags.load(); UpdateService.openUpdatePage('0.2.0'); @@ -90,28 +117,74 @@ }}, // v0.04.00 {'version': 400, 'perform': function(callback) { - s2s.Logger.info('[Update] Cleaning extension\'s background data.'); + Logger.info('[Update] Moving tags from local storage to indexedDB...'); var popups = chrome.extension.getViews({type: 'popup'}); if(popups && popups.length) { popups[0].window.close(); } - ChromeHelper.clearStorage(); + function onceDone(lastUpdate) { + Tags.data.clearCache(); + chrome.storage.local.clear(); // Clear local storage, as Tags were stored only locally - // Clear cached data from background script - Tags.data.clearCache(); - Spotify.data.clearCache(); + Tags.data.set({ 'lastUpdate': lastUpdate }, function() { + Tags.load(); - // Reload tags, will reset list & lastUpdate - s2s.Tags.load(); + UpdateService.openUpdatePage('0.4.0'); - UpdateService.openUpdatePage('0.4.0'); + return callback(); + }); + } + + function saveTag(tag, cb) { + Logger.info('[Update] Saving tag '+ tag.shazamId +' to DB...'); + + Tags.db.put(tag).then(function() { + return cb(); + }).catch(function(reason) { + return cb(reason); + }); + } - callback(); + var oldTagsStorage = new StorageHelper('Tags', 'local'); + oldTagsStorage.get(['tagsList', 'lastUpdate'], function(items) { + if(items.tagsList) { + async.eachLimit(items.tagsList, 5, function(oldTag, cbe) { + Logger.info('[Update] Moving tag '+ oldTag.shazamId +' to DB...'); + + if(!oldTag.spotifyId) { + return saveTag(oldTag, cbe); + } + + Logger.info('[Update] Getting details from Spotify API for '+ oldTag.shazamId +'...'); + + // We need to fetch data from Spotify to get new properties (preview_url, etc) + Spotify.getTrack(oldTag.spotifyId, function(err, track) { + if(err) { + Logger.error(err); + return saveTag(oldTag, cbe); + } + + oldTag = Tags.setSpotifyInfosToTag(oldTag, track); + + return saveTag(oldTag, cbe); + }); + }, function(err) { + if(err) { + Logger.error(err); + Logger.error('[Update] Error moving tags from storage to indexedDB : '+ err +'.'); + } + + return onceDone(items.lastUpdate); + }); + } else { + return onceDone(items.lastUpdate); + } + }); }} ] }; window.s2s.UpdateService = UpdateService; -})(window.s2s.Logger, window.s2s.ChromeHelper, window.s2s.Spotify, window.s2s.Shazam, window.s2s.Tags); \ No newline at end of file +})(window.s2s.StorageHelper, window.s2s.Logger, window.s2s.ChromeHelper, window.s2s.Spotify, window.s2s.Shazam, window.s2s.Tags); \ No newline at end of file diff --git a/src/background/background.js b/src/background/background.js index 22c5136..1afb29a 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -11,6 +11,7 @@ $(document).ready(function() { s2s.CanvasIcon.load(); s2s.updating = false; + s2s.updatingApp = false; s2s.getUpdateStatus = s2s.Tags.getUpdateStatus; @@ -20,6 +21,11 @@ $(document).ready(function() { return callback('already_in_progress'); } + if(s2s.updatingApp) { + s2s.Logger.info('[core] App update in progress, please wait.'); + return callback('app_update_in_progress'); + } + s2s.Logger.info('[core] Starting tags update...'); s2s.updating = true; @@ -99,12 +105,15 @@ $(document).ready(function() { // Check for install/update chrome.runtime.onInstalled.addListener(function(details) { if(details.reason == 'install') { - s2s.Logger.info('[core] Extension installed.'); + s2s.Logger.info('[core] Extension installed.'); } else if(details.reason == 'update') { - var thisVersion = chrome.runtime.getManifest().version; - s2s.Logger.info('[core] Extension updated from '+ details.previousVersion +' to '+ thisVersion +'.'); - - s2s.UpdateService.update(details.previousVersion, thisVersion); + var thisVersion = chrome.runtime.getManifest().version; + + if(details.previousVersion !== thisVersion) { + s2s.Logger.info('[core] Extension updated from '+ details.previousVersion +' to '+ thisVersion +'.'); + + s2s.UpdateService.update(details.previousVersion, thisVersion); + } } }); }); \ No newline at end of file diff --git a/src/popup/css/popup.css b/src/popup/css/popup.css index 1d58778..8d511eb 100644 --- a/src/popup/css/popup.css +++ b/src/popup/css/popup.css @@ -67,6 +67,12 @@ https://github.com/yui/pure/blob/master/LICENSE.md animation: spin 2s infinite linear; } + .icon-spin-slow { + display:inline-block; + -webkit-animation: spin 4s infinite linear; + animation: spin 4s infinite linear; + } + .iconmelon { position: relative; width: 32px; @@ -397,7 +403,7 @@ https://github.com/yui/pure/blob/master/LICENSE.md } /* Updating */ - .updating, .loading { + .waiting-overlay { position:absolute; top:0; left:0; @@ -408,20 +414,20 @@ https://github.com/yui/pure/blob/master/LICENSE.md z-index:3000; } - .updating.ng-enter, .updating.ng-leave, .loading.ng-enter, .loading.ng-leave { + .waiting-overlay.ng-enter, .waiting-overlay.ng-leave { -webkit-transition: 0.3s ease all; transition: 0.3s ease all; } - .updating.ng-enter, .updating.ng-leave.ng-leave-active, .loading.ng-enter, .loading.ng-leave.ng-leave-active { + .waiting-overlay.ng-enter, .waiting-overlay.ng-leave.ng-leave-active { opacity:0; } - .updating.ng-enter.ng-enter-active, .updating.ng-leave, .loading.ng-enter.ng-enter-active, .loading.ng-leave { + .waiting-overlay.ng-enter.ng-enter-active, .waiting-overlay.ng-leave { opacity:1; } - .updating .content, .loading .content { + .waiting-overlay .content { width:150px; height:150px; position:absolute; @@ -433,26 +439,27 @@ https://github.com/yui/pure/blob/master/LICENSE.md .loading .content { margin-top:40px; + font-size:120%; } - .updating .iconmelon, .loading .iconmelon { + .waiting-overlay .iconmelon { width:64px; height:64px; margin:40px auto; display:block; } - .updating .iconmelon svg, .loading .iconmelon svg { + .waiting-overlay .iconmelon svg { padding:8px; margin-left:-10px; } - .updating .simplelineicon, .loading .simplelineicon { + .waiting-overlay .simplelineicon { width:64px; height:64px; font-size:64px; line-height:64px; - margin:40px auto; + margin:40px auto 20px auto; display:block; } @@ -460,6 +467,10 @@ https://github.com/yui/pure/blob/master/LICENSE.md font-size:120%; } + .waiting-overlay .updating-app { + font-size:120%; + } + /* Top bar */ .topbar { @@ -504,7 +515,7 @@ https://github.com/yui/pure/blob/master/LICENSE.md border:1px solid #cccccc; padding:10px; z-index: 2000; - right: 30px; + right: 41px; box-sizing: content-box; height: 16px; } diff --git a/src/popup/js/controllers/TagsCtrl.js b/src/popup/js/controllers/TagsCtrl.js index 58dc832..5d73330 100644 --- a/src/popup/js/controllers/TagsCtrl.js +++ b/src/popup/js/controllers/TagsCtrl.js @@ -3,6 +3,7 @@ angular.module('Shazify').controller('TagsCtrl', function($scope, $location, $in $scope.updateStatus = ''; $scope.updating = function() { return TagsService.updating(); }; + $scope.updatingApp = function() { return TagsService.updatingApp(); }; $scope.items = []; $scope.totalCount = 0; $scope.filteredCount = 0; @@ -29,31 +30,47 @@ angular.module('Shazify').controller('TagsCtrl', function($scope, $location, $in $scope.shouldShowFilters = !$scope.shouldShowFilters; }; - // Status : 1 = just added, 2 = not found in spotify, 3 = found, 4 = added to playlist + // Status : 1 = just added, 2 = not found in spotify, 3 = found, 4 = added to playlist, 5 = not found, manual search $scope.statusFilters = [ { icon: 'icon-check', - status: 4 + status: [1, 3, 4] }, { icon: 'icon-close', - status: 2 - }, - { - icon: 'icon-clock', - status: 1 + status: [2, 5] } ]; - $scope.tagsStatusFilters = [1, 2, 3, 4]; + $scope.isFilterSelected = function(statusArr) { + if(!Array.isArray(statusArr)) { + statusArr = [statusArr]; + } - $scope.toggleStatusFilter = function(status) { - var i = $scope.tagsStatusFilters.indexOf(status); - if(i === -1) { - $scope.tagsStatusFilters.push(status); - } else { - delete $scope.tagsStatusFilters[i]; - } + var selected = true; + + statusArr.forEach(function(status) { + selected = selected && $scope.tagsStatusFilters.indexOf(status) !== -1; + }); + + return selected; + }; + + $scope.tagsStatusFilters = [1, 2, 3, 4, 5]; + + $scope.toggleStatusFilter = function(statusArr) { + if(!Array.isArray(statusArr)) { + statusArr = [statusArr]; + } + + statusArr.forEach(function(status) { + var i = $scope.tagsStatusFilters.indexOf(status); + if(i === -1) { + $scope.tagsStatusFilters.push(status); + } else { + delete $scope.tagsStatusFilters[i]; + } + }); updateList(); }; @@ -173,12 +190,30 @@ angular.module('Shazify').controller('TagsCtrl', function($scope, $location, $in }); }, + setNotFound: function() { + $scope.newSearch.tag.spotifyId = null; + $scope.newSearch.tag.status = 5; + $scope.newSearch.tag.image = null; + $scope.newSearch.tag.previewUrl = null; + + TagsService.setAsNotFound($scope.newSearch.tag.shazamId, function(err) { + if(err) { + console.error(err); + } + + updateList(); + }); + + $scope.newSearch.error = null; + $scope.newSearch.tag = null; + $scope.newSearch.show = false; + }, + selectTrack: function(track) { $scope.newSearch.tag.spotifyId = track.id; $scope.newSearch.tag.status = 3; $scope.newSearch.tag.image = track.image; - // TODO: Remove old selected from playlist, add this one instead and save tags TagsService.selectSpotifyTrack($scope.newSearch.tag.shazamId, track.id, function(err) { if(err) { console.error(err); diff --git a/src/popup/js/services/AudioService.js b/src/popup/js/services/AudioService.js index 7c6ab8d..74c26fa 100644 --- a/src/popup/js/services/AudioService.js +++ b/src/popup/js/services/AudioService.js @@ -1,5 +1,14 @@ angular.module('Shazify').factory('AudioService', function($timeout) { var AudioService = { + setup: function() { + AudioService._audio.volume = 0.5; + + AudioService._audio.addEventListener('ended', function() { + AudioService.callOnEnd(); + AudioService.clearOnProgress(); + }); + }, + _audio: new Audio(), setSrc: function(src) { @@ -35,8 +44,6 @@ angular.module('Shazify').factory('AudioService', function($timeout) { onProgress: null, progressTimer: null, - _onEndedSet: false, - play: function(src) { src = src || null; @@ -48,15 +55,6 @@ angular.module('Shazify').factory('AudioService', function($timeout) { AudioService._audio.play(); - if(!AudioService._onEndedSet) { - AudioService._audio.addEventListener('ended', function() { - AudioService.callOnEnd(); - AudioService.clearOnProgress(); - }); - - AudioService._onEndedSet = true; - } - AudioService.progressTimer = setInterval(function() { if(AudioService._audio.currentTime && AudioService._audio.duration) { AudioService.callOnProgress(AudioService._audio.currentTime / AudioService._audio.duration); @@ -75,6 +73,8 @@ angular.module('Shazify').factory('AudioService', function($timeout) { AudioService.progressTimer = null; } }; + + AudioService.setup(); return AudioService; }); \ No newline at end of file diff --git a/src/popup/js/services/TagsService.js b/src/popup/js/services/TagsService.js index 8e71453..8fccc4c 100644 --- a/src/popup/js/services/TagsService.js +++ b/src/popup/js/services/TagsService.js @@ -4,8 +4,13 @@ angular.module('Shazify').factory('TagsService', function($timeout, $interval, B var TagsService = { updateListInterval: null, updating: function() { return BackgroundService.updating; }, + updatingApp: function() { return BackgroundService.updatingApp; }, getList: function(status, offset, limit, callback) { + if(TagsService.updatingApp()) { + return callback(null, []); + } + if(typeof status === 'function') { callback = status; status = null; @@ -47,6 +52,10 @@ angular.module('Shazify').factory('TagsService', function($timeout, $interval, B }, updateTags: function(updateCallback, callback) { + if(TagsService.updatingApp()) { + return callback(); + } + // We define an interval to update the list while tags' updating is in progress if(TagsService.updateListInterval === null) { TagsService.updateListInterval = $interval(function() { @@ -71,7 +80,7 @@ angular.module('Shazify').factory('TagsService', function($timeout, $interval, B } BackgroundService.updateTags(function(err) { - if(err && err == 'already_in_progress') { + if(err && (err == 'already_in_progress' || err == 'app_update_in_progress')) { err = null; } @@ -83,6 +92,10 @@ angular.module('Shazify').factory('TagsService', function($timeout, $interval, B }, searchTag: function(trackName, artist, tag, callback) { + if(TagsService.updatingApp()) { + return callback(); + } + BackgroundService.searchTag(trackName, artist, tag, function(err) { $timeout(function() { callback(err); @@ -91,11 +104,27 @@ angular.module('Shazify').factory('TagsService', function($timeout, $interval, B }, selectSpotifyTrack: function(shazamId, newSpotifyId, callback) { + if(TagsService.updatingApp()) { + return callback(); + } + BackgroundService.Tags.selectSpotifyTrack(shazamId, newSpotifyId, function(err) { $timeout(function() { callback(err); }, 0); }); + }, + + setAsNotFound: function(shazamId, callback) { + if(TagsService.updatingApp()) { + return callback(); + } + + BackgroundService.Tags.setAsNotFound(shazamId, function(err) { + $timeout(function() { + callback(err); + }, 0); + }); } }; diff --git a/static/fonts/Simple-Line-Icons.eot b/static/fonts/Simple-Line-Icons.eot new file mode 100644 index 0000000000000000000000000000000000000000..f0ca6e8cf907213894d610112d456b7804fd2800 GIT binary patch literal 54266 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 E0dl-`Gynhq literal 0 HcmV?d00001 diff --git a/static/fonts/Simple-Line-Icons.svg b/static/fonts/Simple-Line-Icons.svg new file mode 100644 index 0000000..4988524 --- /dev/null +++ b/static/fonts/Simple-Line-Icons.svg @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/fonts/Simple-Line-Icons.ttf b/static/fonts/Simple-Line-Icons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6ecb68683477ecc5aed38ec3fc8910d9bb66276c GIT binary patch literal 54056 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 Kj_>Q2Zu>vsTUp2e literal 0 HcmV?d00001 diff --git a/static/fonts/Simple-Line-Icons.woff b/static/fonts/Simple-Line-Icons.woff new file mode 100644 index 0000000000000000000000000000000000000000..b17d69491bf374d36e07339bedd3349caaa30d65 GIT binary patch literal 81332 zcmZshQ;;q^)3pbCY}>YN+qP}n_Uy53+cx)D_t>`mJ@0?~r)pJdU6pjwN2ye|hl04c zf~u+l5HMd05DX9y5D1|w5b%HV|7VDZixUC?0Yd@-bBzLlV3j4yH13FttI7cZbI<>` z9QS|nNys;p5ET>uk6r!e@%|G&5Czbvf(ipO5HJtye_rN48LIbaE}7UFIsV5q|MQCf z#Wg-foK~|mGcx^;Rs6?T{}Ve5)vmPVfABxH^`9sFPo$8fpgop$uAcv~um614e|tlb zi{WbQj6DB~$J_BA1OI>OR|K**vNQXS4gBX%|4R;H{UV&l(ZS`v9Qpot#{cd^2m}fQ z2c@gFvhxD?gvA9%LPDZ&oFWn-VPk<1WMO`E&Yr1}RYZjj0y+hDoQR#dY^}@8 z%t|#oW@D+(Br5V+naXjV0A5IR6gI{Gr?vZ7(~Ds3b^F;z(4W8A`?@i#{rj9>UJF&N zW|?)8pT@{U#zfD`L_|f;YGmuIoHg%S7$s2^uuz?#RTWaR!JlSJVAGI(m&02m;qE>h zpD-RQVXd*uZ(>6dYGP|*8+JFm`lyR?({4-Rx@-1h%#(jY@+F3|AjeDi5hG;W;VDlV zIceOdNzIR#A6Y+&@1&Z83Ra}4i*h_v>SZa{B6v+-Ta8`@w;XPxU_EDJ(vse4YS+cC zp;ORX_^sz7;bZWrs8jD(X}99sst54DrnruLsdg9o)a$NNvvSK?_Ion-hT#*vEr_^y zy##%N7E6B-5<)@_~V{p)W8S8rMT5CV7hh~h-TTLb}QjCseN}Q`8FFO3B z@mm?&7JVA}&a8K=A04I74m)}3+deFZ7l*E|ua9)JyoGj*I9Zvf93CVIFPS)!Z}UoK zVe_G98kSjFQI1Dahv{hiH@?K0F<%4!PUqUU2};xgCW;yeTj zNl>Z>Zi|y)_WL4w}v%8$`0Qk7ps;WDJ3VDhs;ey=4vZ^u|C3i$jD|S?E7J7 zfqXc&>kpDB$E+|iMv5iF#%@lG&clT^H*T*>ZDn~iZGw`#0zND)o5w1@Jz2^UPhRRs zz#bV?utZO|iPnqZX2=oSrEFC2^ z__IC|ifW>u#GO}2$&u2M$V*1ghPEER0-j{ZYE>Z1SkD^FYloeokTZGh3B>7)As`1d zNk$Oy9vkPq)pRmzanzK(*?Zn8Qo7Fl!)$X-IV<1ejh@-VzuY z3qpx?BZz~jmhlNoV6B%0cd9gV$(NLpSHKjQ^((=2+bT&pDmhw)Vv>SdLW?~tiXS03JW%2+384Hu|JdRro_GFU>luD97h+=yx6XdMOYwvZw2 z$h5Y%w6_LWP7{u$+uT~yw7j;YBA!*ZtN^gDLG9>7KP3R;0Pbs)YZb`%xt6(Bh88%0 zKx-W0TXsu*%Pe3GfPQU&__J%d+2YW84e-#8Zv(9wkf%eq7_h(gu}gSyu&ZxFs2W57 zBq$=lTMF1fw2AIC$kM}9*DdbiJk`fGr!64YJ?=s+EB;H+tufk2 z%TdPHN$y(RAgu>k>P32M(ych!nd)_FmZ^%YRjt^y=ojL>C(F8u+@W>nUG@vLU2^T0 z_+28e%e}4+lq7Mk2EC0p?&?i+Uqr2h%> z=_VlZ&<6G+b|I=8b<*N`K-EtdEhG%xhFfwg|M92N4j>k85*>Fdn$|nGaVheNI^$A( z@{;GhQ@Ae>oF6?;iPn5Av(5bar!?dg^2L80BbCR4vb ztacvBLO&ROH{O;xSRye#WHDB$Sa;R(eGYr$*70qYwe2VDtH$7Ii`nubW{|kDCw=)7 z*=sk_w|x+SdanZEB#9!S9&=cFpb<0S5fzMf@a1){6W-w|g_?k-5g%nEb?{MqFea+> zo>kFbp@q!$i9CCsadHg?regDg4?5qpf55??GZ~!%$cB@Ww&8pdtJ?yXcc0VY9;7$MzP0MH>fJd&?!(e#_ zr>yrktL=l$&aR|Q-ZFn@zOSRg4!6n=t=`T{?^7eT?HTDh`v$;HKzC|pH+~Da$pif4 zx0nP08bJZ)KY(}PYw0w1Jxeo<){Ew>B@Gw(C0+k48a<2^_(!1EPOZ+PyWS{*^i;_FH zv_qqR#8gR-pHlb2zGbK}MUTz*-bk65cD8oBum0@ySk%W5)96wQn8is;Udxq*gy7G= z#*caz-Q3mWS%iz(IC4wLmXN?Dr>bB z`-0rc$PT+?cXrS^5w^8)##>a&F&pRoHolKN@Z}x0VII@*#@vIsim{lW>)^L!MprnQ zIoNTh!nPZ73&QgBZ2l7JC^n~f>8lvk2llu8hT7*g2intyE%3P-#O&p7lq_^Y?2B=V z=z~7>-8?b8O&s8T@2MNymGt7WfZt=L5uCDyppS2nFdW%>gozoUEjwTjh@aX3Wk7%= z!(VY5>S`rGVl{}ij{(QpL+Q@+pU(ZQK} zW=>S~cLz@QMd^il1qnEsdfC1A(#$2`2f<&H%;DjF*XcvS$k%dU0w-o5NE_QbTH*5= z3pASy!pjlm)K|V|qo2$sImZ7=*5CjVibo4a0qUO3M5J8g0)Ko0S(MS()CKiy>bgY) zN;$O_qAbF?t%cm#XC!2x>A^p+1|l47W@RIwia~c?X@g^YmF9{!hOgG#Zgec@JaHDE z{LJW4k=w$TpBYd!?TMmO4!1xKVI2RER5aH@g0F_Z0$RUFH{B11brL)_8Z^iBp0b*6a+WOtT=`K?GsuV8pMm^K580T{hl@cAxdRP; zZ~K8xRhEnsr&oUn(txF#NEp-)5@Fcd(ctXB` zdLk2hgJYze-xDCj!I*G-dw`{}c{}tW5JWSL4wq77MF?~QJv@?CdbP&~+6iHQjFkbl z#V|JfC20^>6ol#bvDal!MG*W=n@KYoqk}&@e`Axq1QdoU0pEdnmqJ zEB#85wLFvxJZLZ}fP$t4C~UJPb~MXRtz_qeRD*pTDJ^#HniovPnR#{>dvX(^MaI#b zsWEyda=xHv_?TsmxG~V4sUcR>tS3iogo~ZZbaq%0zpp6k3tl*xAzBr=J11ab#%-*o z1wZ7BykxZxwp;*;C2C)eUatfnA%1#qOzhrz@QQAMPKqP| z|G2g8r3Ijyc_TjO1PJoUzF&hb{&+P?g%?E#bj(`hW=kT?R@Ue- zLri;HPA9MbJirz>@}c+KTq({JAwK@ah+#U_@ zx`7|&iC~y3AXFV;rR(ocp~_B8J|dQEqzGm2NDB|?=_M7%U_DN18*x4I z0xouxS}nLgT~6ph?ZQH2La)A8hGjyCZu)F#D9Ps*Nz;E@qUvhg$v_{j*-$ezmu|c_ zC?pt0PUx}!$!VxAJ=8zeYm;(VdNfvgB)FF69`1T+-oNx7?qc}Vw~ivWdO4Wh?X`8@ z^Qp&s1mi`2^kDXx`_IK>pX+qi3C!aW2tC4uZ6t8UT$!M4N8Wr1$|lpsNy_46-VXfa zr8}x+M9U^Js#$O?7w)V|$%C!IZQCuZR%}GQ{x`Iiya>NXQq<3YsH(FQPA=pZB}?D$ zoSpUiKqc`8`~s7r)B96;08W2G2bLHizjWReeStN?65Ahw6>mAPEVI4%oSq}=V-#7A zC93R}iWP5*5oNRzR(JbnQBJos+T8f^A$#3=5zo=$w1;Ixb#L)f72?MuBdeN`?Xlm4 zI@9#GOQA)*X+?8=)3u>9&PDie+&Uzp?G?^BV>^LSrzv5Aq=v#ppwG~ z8HGJedc#+R5=>tiT&;2gbE*xp?@v#EAIONhLNraWvr-xvs?G%aNcr7lL4(huECQ9j zRlvjvKTmhO1H=a7NHGv6jGEuEcph=WZ6O6PyvVD=L$G*75uY6iLm4onc^bg#t9ns;`XV3I{5LS*o zvSr<`8KU*{i!d`pw`YHh$pN+nIE*$!WryV4J19Ec+|?T3dxGdQg5+%Y?yrB#$T6v< z+8d#@?D?kws2>3&5M%p^{soI2N|`#@Y>H!JOW_S`HG3Q|ore6J-o&3`j}MzW($fq} z2*fo;mCH}?B!joR;raFT1C3uZ1Oc9xU*-v$gWWcYEivM}$Yei!?s(kXtk-yA?2Y%( zwl9zHl8^W#3Z;Y~7pH@4uS{pv?dRL%JbAwHvhdpCcgRrgQ%>F#Ujr8e^RqbA5D z;jDRwsC(%s!>@m@L)r06cFFApJ)N==?8Fo8gmk9QrA+ss_~$mW5$McTZa=j?D9V-V zDK)%sz|55%<(w9qxyHz!54mK+=;(}}rqn_Z7!zX-tgTKM?}}e2>k_s34N?Y~_-i3| zj6h6y2v0mPFcX14=TS{lf6XceR2PHjl8x09kL};)+)n9cdws9dxB|CT@7+Qg^6;G; zy96?b4Hxo?j>FayJ#~v+k^HcBKj6_>MLj3-ULc#E0?5%ng-v_Tti-wYC{U6K5!A3f zQ^!X8MTz^NAX8nSO!<^r+stIWpn3iRr6{%BOrv&5mUAtJ-F3>&TP|SloC}j&6YSUY z8CDpoT~r)qTF#-c&qE?{EM_FXUj1lVadcW?(-uGR4J~w$N@w9Vm>oB3x>a^T->?Rc*I}IquLK|FE$Z`a z^Tf8#l{qXz!g$WD-V2zs52_5vksq=u3E_eJgRxC-fw_c{ z5J#YpX!xuQxFODHuqb7iHV(jKD1EI~@~P=t6SowL^RYCopMLQr7L%N$?T#XmV?PVP zB&}N<#&~h6&cCP<5IVxOD|=Nx_QXUuiJ`2tLeM@Vqbb#>@E3l(SLFUmPLgiz>;Mw(xVCTTUcBSVK;74txE}4q_uOj-S-!4#LrEC4BJVTB1JH@hR zU0}9KGv0L*TWlik&p%+9LV!fmwF%nCsnEjAr#725i)k9P^TQu?hNYHN}@m@bD1X@iARPe^1Aj282VlR80 zg03UU5@s1e95mo9CWQLX#g8ze=ndAI*T4f{O{^D^by(H|H%B46LcJ#}4(ik`RbO&O z1FxMJ!)4PCLi5NQ>)}YK6n#OW+Tn)DEwK&A>!fgpwyCdVnxY7EcXx#mH*jToX#U6& z8nWkk{u(zB(wK&O76AE5=g~*9azzh{Z`+C~6_imbc3{D9aE>IoTmItpl8Y^sdOD8Y zVqO8ZGd7Fx5qa-3X8c4d$WZ5byzIp2$yIck;bSq-XL<^G2?rze zD~mD}Zxs{R?TmeACV9vy*X5ff7SW{?0kaQTUpFXS@%&e9+qDiW zHM>L$nq=GLao7lq4*>o|!tzUTW`g_f$=z#=DWQ}f0c_gzcA3od+}qeNa`7<0(*H}t zR3!32|4BFV7LZu_;7ZGApm}itWGNowTe)@%Ru@UgDEGcZ`>z&{EvM{|n6v#Z^A9Uz z+xaH$_YSX0WB0#`v`&z}a5N3c5h1d{DHbYBdo)kFQ7?^(-TqxTJ^T1wq6vwSH|5lx z=)ux&i>@^#)W&*&76ws&Fluze4P>a0?Qis=a;!cSWYAoUDu|8d7?fjgHgSowNLIW+=Bg}hTlyz-l|{^y<@EDIuyktlD(0UzjzBkBP{o6$juhUTJAYD;dw84;?iC1^l9`WVG0FK3rn&tq-W;}-sWWuNFv)}@ir!pA0 zE~MD+s+5db0g~6vbcj!-h=w<3yb@ErDtQmvsZpQ&afRL9%(R#{_ zW+7wexPdNc=lLWB8`3wg{&b=5B>0)y9~{E{i`AoM1@^wB3k;-#^deU{AH@zsgr8rG z&5+apeJi{!DTUNjb7f!{j=LI~WR^jk0bE#u2vGM`0o`0Y z($AfW51!vPP-E?Mg$ zPlvwY!Ue9n+0NiOOU3-u-aIk?a-*tq*RQRq-xRDZLU!KC4*S~uZ_xJd!W4|UOl?tL zN|NQk)n=&AHFT@lo0?NG_c81g*)FlwdWFRbCX*r!_{A(x4tnxKVFY(zlS4VC#g>An z()c0VP>eq^4jK!A_0R+7-w31*ZFyPNybqZBnQkq#<%&vwg4KHgi43^#p;JXAx@*Bvf(ZNsF2OJs=Sl4d50S*Ai|_ zOj_glW+ajK#ZI!J>-cxq9WqUY`=!#aH88%mTIF<{>@hb)%mr`c^ZWmxdm34%iG=zd`^_CEl5@G|(M1bfw0{OfmxgatZ1 zfZXYe7i}!36iFy{a@kDGxYg^IC=y@z$*dP)Z-$`pS{|nVpX&Fx9ZBmr2G-G=W4q$U z=Qj}b^3XgA-nN07L|p}ha8>RU!$KX`aCr=5+1U_8m?FmWL{QG-&D&ugDf9Akmiipn zn$Oe{#dh$uZ8eZStO=^N!ZX_dG-x{#?)9#^_xKzSrl+cD*xOtnov^xuaopleeX+OP z(kp8PabL%I62(vfAfg|{91)q;bAF(MM@$~H{kkD~J&lT2Iz6p$lvj+WBaqyrB937d z4X#jox*+=ZBjnjkT0@AS$N@E=y6m+RPi4t?pnQU)*(4Qfl;;Azpewxjc zKDX^a>>bqh)Tsce<0Kn8TqDgU;j!u}gneD4%oGAFQLbS({jj%Z=^Ngf_0m4jvkS`7 zE+r3+`EPI$1+`dH4aGUili@k8aK>8v$<+{UR-ggBhY+!$^d@qP#OTuY5>4RCo80_SFXO=Kxt45t@ zT0wIjL(b6YV2G{YFE$iQL*8y_ZSaf5Nib zqXwz^*>X2L(iX0GqnbPZMfKAlU^!sudLD4SG$BE)xgVc_a^OCAT0jyX3+ENTF2NZV z67zisdDR`20k>kHYv+I6CkAYJ^#6ImkaXM{4bz|^q%Js6kXiPj7RMUcuqJ>JGaBFtSBT@QN&|Rj zz5GO^1SzO1i{a=3m%F0Z#tlBb+Fn7rJc4!2z0wUjjuZ4iewE8?%>AVvJyN46jdXbC zIoh1_ys1&ROywD!0|;v~lVAkPm~ZMJ3PubbeX=;_NP)Zkt=Uiin`ZbWT-KN5MW0EL zZ6Pw$%V&;=mVbC;wAzO`!$lbCDCj&TlcVKPg!QmYmlEO1@bR@KWMt@vc8SG8B6aNn)nb)_1MLlO}xAmBwkkc-L zWMO7v9YyB*OBamO4u?ET)u}T(`N<}Vp>Gq*jS$0O&4FEf-er#7oJpw3GH6C%BA_YD z=@A3!FcA*;N!(;h8KzJxw!asZq;Kkb)u_bYn^IMMD(tVOOV`BNzU_A>YBfRsL1E6? z?|Ib<+z#{7g7||9&X;`oHzoj(f!yJ`R@slZ_*sdp)zbj0F-%m$YL>lxgdMBTjiFpz z`q}Ah6Z*hxOf7_#=ysrg5z0=jkUPPk!vK`!BcX(%K)jR#l{zQ=WjnIoaknK>UVn=8 z@$q^^ilmZNzQ;E^!9;E8$3cui#m`2V4fTgr#{jVYkFGBobC1%DT9JvWGoz>q$*}M(8-|*anrPE%5f+B7*`uc=XMpD%MgL-i!WLTyL&H`FKoV>_HV?3JZy}c%AmY+Vgt`3Y94+P8 zrVFrOtpbJm%V}tP#t8NdH>pEQH>u?wrdv7!jy)1ar;#1m*kTQHydrAFId7qtryxl{ z`^O1>`=pThHc;}3(_*CD3}!I^F;uD3^ve_S zO;$8rI;TOj{>1JyFkjWj2ocz#vT347)*5LFZG9iB+~|>Y&mVH<@B^v7z(>DJKC4}N zjVLSB8OnW)T1|B^m_L_iZXzZHBht3KaPpu^w}P~XAYqEkBS9-!qzLZpf?PYm{D~5& zdbKz7q)7o$b_3a37yV$VJZ-t) zAa600Gp8Z>*-3}Dc(bvZP=pd*b|l_R8Qt+Rs5JBegmU?NR|KPR?(q|UNOU05QEZaJCRrTA-kz;h|_>&$2_hMhKzU9YZ3o4I&65Z3{k+Ll=Stn!jJ^VB5 z)KEJ&37}?jeD!MF*2dhFXfJy16tV6#wWz!+eoyDUm_+x_pP!=s^@)^%0Fy(efCdF~Y^BVhl=uLq)!V z3rQ~UTlKIGH>4^M^pF)mPyeKqe~Blv10=H*^)_V4v7hZ}f(-_WcP`32W8v@~;sJ;q~)huzY$+%*S-rrk8Ku31! z26?@nZh}p-t|$rq;bH*j8($*2%(aCE(RYpGJsoZ+59GJ9vnur0d|)-aVGdhN`cC~RNV)ZI@%T4?8yI%q z=l-4Q9CdP>b*Y4OdRBYLsU`#=zpFqG+RGxM-T=K-^l>KM*ybYE(#{{ZO(cU*kiO0{ z8xAaqvCvO;2G+a55dRR$EjRc?JL@E_m7oDK=bM;s-9ZYspr-@?fq8^Ts3oOHd8SR6 z&eN|lA*O>dtGI1E97h}FtM-JJk>+MM9r;nI;P->nZ9a_Wm*n)?{!O`6yBg9%lg+O+ zZW5`?HRXkwP?PU^)daa(rqAO{h-mGv!Pg#`%PW?ce*!l(n*(w1a^XKp<|w) zV|V!NMm0eAi9ih~Tq$9)ucmqhSuqjfUdEC}%|BfGX^;|&aji%VQJ=4|hJ(fekpoNI z`TH2>3T54`$u-fr4174yURNf_g1-<7H8&4iY{jiRpmXex-M$F~+WEsG8FTtzB!1$& z!HUVX_@2Uuhxbnunmv_)H&z-{MCN^-Kdc@G#xGueR9tl$TKiz_cm~zB9jb-f@ht&`iseC1ISqfFBu9qezDFD`A|R&n4_vd>O1VW~og9%=~uYM;>}8xzfx?ESbw z=B(2Y~FsU(w>aJXHe{L6^BwFPy0A>wNr zI4|r{En1v~GqDyvqXbq75VG8o`V@<-wf$&~CWX>W+Z_=L*Y*o9$_&|jZ3IEf^-`5hG zmHrS^D7~XHaMm}?d1WdOgh`Vxbmj}zcfTGsNXvzQjcnQOlZSW3`g5^B4ikVfoZB4Z zr{}uSjO*0igge}r?5^Jz^s1CovG2@#yQ*U|blJzRxK90*RL#M#-gkp^goEV!Gd8Tz zj>Ti4q2d?1;_dv!N$r6B-0d<5+X3MvuE+f~3 zPKB=my4~IAg#q;U=v}o&`Iba;UuskQFy5H(5~r8whxZd$)$6di!qPMZM$>x+LCJ8V zR(~2bH4B#n&A`)f=e?HquM`wz{4IFk4|q14-vuOuTI)L&XFl@K^_q; z6m;72)6oJE+2!Y=J8OHdRB*8;dlGK=hYggJc)b?A9t%uS17l%ab18nWxj>qID@m+< zFgY#KeZjwa=M=w4WmfV~4s|eYpqkjR7PD(wiF=r029cOK#ZB4^CoOWEzd7k;TWC)? z-f)vFH4gjbUodnUK}#_^EeUL0oI{qRpYza|;=>#H@&n_XffV7)*>aue&XGp?ogMQb zC)xkK2Iwb$-(&GdVDr=UkKMmqp)smJW6|apNVAk$>9?NhPTL-yA_<*@35j&_mf z=MiSn98(v#xh`8$4STn!S|(opBbxX*s4~Kd3RLoD|6#-qUnl+U&`83xc!qOI+N$XXH<$a7^-b7ajBb)ca zL=)RJpWTAe%4}(%cknqgC(q1eb+uCkE_jmlX`q)cDZy(PqsYf^@NMcUiX)o?z|8_ zTA5D)@aiBWBF{X_UZ@0X+wpKu?7i>&m7FTFa*6&G+f$7Egg@R8G^+oLr9;^CO0>vP z_c-L*Ct{P>u9WG;`PUs_sy%g4xT)KlwbDUQ|K&k_t4$`wa{F;|_?wb^t$fHVdu>Bq zu1%(t&9&moJLA=Tj(X$(2L&KXgk<~`8{5apaEusa8Q@R5&yZW(EBV6BSFT zOv5FIux533{~!##jjYz{u^F-`HN8o_=PU3 z`!&e0{>3U|K0?nB_mk;>{a}$*aql+jV#^6E%_m|y41ik#l$V&vYFhLKPlfFNF%_E$ z%S&M_4+vd2oDpzLigLejML6Ve!_?iCnNs`oP=D^?0^RHEJ5_y^E4)2Db3Dl*SC)4EPxzX+di}apS7R0XJcE-JC0aj> z4JNWL)zTe_6FVkW^63a-74tH|AG=QIRGe{V(K0V42~jS|5`kJUZV_Dz$|ucRKw8^r zDNBa@E$_^YZMrwrjoyGNKH9vcsDdgQpA&pq()K!c;wmpmxJD+*I#ViJmsnKdfkL10F*>e1W1Pbu{UvG#K-pcZpv_9t zfspJxM$zPaZSpiSpXH{?N>;coIowRQ7Zkvbl2Zq%k(ejkj&RC}EnoaCz4f=F8~b$`fQ)LL7CNQcY< z8dCO%y5$f=5rHUZIGFK8%N4P50kst(U*N%^AYj?gCJX3*FGj(H@;aLk z3T@l1wZ4SG^k{W8XKZ}`i(Z3TR^qY%vwm)cQT+Vbs6M36x!E(^Mye70P&KT~D?M(TSwu$vear;P71C+GCu!Cpm9+%lMW-7p#!9?hT~Nh< z2eiZcM4u^EuYnl?vTYT~S`MT#83KG{>VH{5G4D~U1`N?dd}?0p{9%hm>ZWolX@YS0 zY^Yqd)vsdB2cI)iVHqK3j%~)KX6%%Kdwka<24t}I6tXT07Tet~dj3uw?z$F)H?{Wd zS#B)C_28}ANm?+b=qt22sji)PSVp~A0KQ%3ZvL+h3?A*W44*Jz0cJmJH-&zrNLzD; zIRJJEqn=IGD~-DE*l?gl>gg4}^*D-XE3}&RpW&Ti7dLaJVeIGi*f(7JLlIy5ZpFXj z&PUtPIxE%JKVKY$rD6R_4NS$1A;!;$km|%o3Zf zB6(EQ`Yy94%KIYv0`$_bKAeT&`0Gm@RNg#zw7J;o7JRjl5I;*|w#&4>e%uokLyZf;xqoTNIkwL?K5!W(>EMVk-EM@`n)rApSA{9d=zVvhbmm?q6L$B0R6ErDM0orKktsl*hDT)yzJK}OBFN7A6VBY$dn`)Yb2&bD7~ zQufQ#=)RgiE3@BU<1}QFrFbk|JYPXXDRPSUPR(EIg#KMeNGz`T&Kc)fYq(Uvx11WL zWH?T`^ufptrEFJ{aYGc%K?_q_DLSiQSKd>x*R9DwkY(tII+a;qs#rM-d#6Q|`dsL_ z@I)b@)^fyj#yM3PKxGIHnR|G_6A+sMVK6f5M3OxZNypod8GI1BaC}H%uWKmEHZ0ki z3X9Z@>8lz~X@nMUi@0HCE?FTtt5yYC1vdr8t&bW&t+Sl}uDQ!11J$9{2BE4mG~*hn zSRl+de&w6ZJ`z>pQ9@e~?%#84Jz8C<@ZI3c)@%(!^qAeAQ;+;iRIi!f?uCHZ3r6EW zBX2YrT**%(*|saKG_Bjw#k5cUy@t{NO#NMhHmkf4-LxW)*Mi2r(lTt-* zIPxGd&>#PiXQ67(P85L$Ek3SK+mlM~$|G?rbUc{PxpRg3G{FAZKL}1MM z_EG}>NS>e*B`X#*BT=y?JO7036aw@6b9zqN)5AM#IY>OGB)~cO{ADyLoNRKvI}!Hz z-_Na0Vz6Ee;vtlUIxN)M7>l3|xBmEv-{&ul24lg|Z=&BF-e_(~rIlMSbDLu=|Zt@q2$n8@waNWmEayWh2Jgjbq64ZkM~w+W+-{gHKEB z(zh>P%jcn06nk&ZCo8F1t1WtVkBCtwafz@`PV1byB>h6KmK1Mu?W&;@JjTyi0{wIU z?Mmgz_*22|_sL5>$`9Y&<%zxbc5|hTko+Fi%Cl8n<4gsnGZe{nS?9A=*#VEe)~UwT zzP6e<8V1+ucAAO-Lb?B7#GMYjq!tUuFMQ@Z=@sYo1#T4mO9;X_-p}io8^J7U9di=F z2v6)gv@PxF5v(V{s}4K$F{7yOG-?Nuf`l6E$xPQ zsg+T$ClS)d9Lrs6!#azwMhOxZ&MB7o7fD_Iw9}n1Ld3|}OlcM5E?L=J?3Mz@pzEyO zHFaSe9LwaSqv7N4IkWk{*hlI&5)NEYlx-X53L(YxN{jdS1>or|d?JFP$f`n$fB%>= zRxVp%2@+9ftzFlzx&%CNIc7GvhMDpUBaQJ%4sC^XORv`DBW25q(9lyOm{Pg}Int6g zuChRWZlz|&#Of0{Mk`~06>n*mj7WOUH6E|xkvyLW7&TQ95^nH3&Y@Q_00+D8*IuaL-jJrl+Pv zVk0$VO`)2{v@@7$3$r@a#((V7^pRLvVtJimZ~Hw>{SnDiV|lDd`v=sS@o~tl9aPVZ zcXXPgHM3oKR9((7H%zeDAb#4kV4`&_L+;3MU15a-QsNmdeMm#6afpxejf4LfL?a{9GuENR(QLnNQaefhfWEuGizT*K^#V;3!6T&UHkG%M9^BRV^|{E-U$vJhj|P%gw$$yJMcQqQ2<_EP!M01A&=GwCw`+$uj zhV>+9FjUe{e)D#_)f>+DLBXv2WW*|^B5E2>e3q@X5q@wNlRFS{&SLA830sjkchd8d zW*y~v13H7@DhlZz<9SO8iZU!Gy!|JT)DZ)lENNT>MOs$SnZ6% zhHc4RO&h_OQo8gy+>4TOO&d6HBx&`E|EB4rkr7Xu3(vJ~LH1sD(Dfi=)bx!9W+mQ$O(l&Uh!CTHCc_8snzIBDiG%UL(Cj>pQ z1w$_Bar;zeZI9NoofQ}pO_x0mmp|TNX`PG+zag{9v8DAxHhA-&s6p>Eh2=y)jplL# zy5__F?V%}!i{jo)d|DqnQ;;$Dti8dR|Jty- zGnsRlFbZoojG83;==l7qAoR|r!hso zmlt|14c9f*N^-yNDJrefPfcF(dM3pERz6K;Sq*sCu2i`SI+}o{&z%>}X?nyh#gpn9 zmJIZSsujR=r_|=;Qw0PSml-=W7ATb2{8in0FukIzk1M%u-g*oReRR=+5Z z`An?>aE#0pL>{~gtk<=`*p~tGaH|(w?2BmMl`0UvkVGZ z^)jgoYmrV@YNs$|jSNmRvsYA@82eMf`NPy6QWHm5D5ZrI278s%ti*24DBJI0zra@l z5Q6JW@k6a4wF_HnG4<~*;wLORM{J_J?U2Vlt>?| zoRU&UGpnU9@^$WkprN7!!xlL+@7dDfmP`Sq#*T{FL^``z(lHdIGWPBv@u!$UvbB6x zulu*XJ5R%*}i>Csnu5PIB;JpDGWOhAW&5^)Y<6xH_kyxcZ^nav%O! zT;0E+xO!(u-O=p-`q0GL?sJU$|CfsGTOSI;xd`T(B|UW`RksDb!E6Wbg}yz~6Y`Fo zGODQgEn@m#6)0K0$;{s*Qk3#5Dm(fDS*6MXs#}D;E6QO*#Z!4a>psTX6|l5P^%A_= zFN48r{BVrTj<5mLDyg5Vy3~3`FV&`&{;673Tf;^#xROpW)s>WwsHO!_zzT1cR?2I1 z(kMmn${d%HJElMD!WK|c21}Ro%c?Q-#Sk!y?RS~&3hEhmqpUXixhYRq{Z%r`zc1)U z$wRbQ+;TKj68be>kNryqxE%|8R5o-FopR+Aih%)i-fk@fD!oZxNE14pQy?Ei68n=S z{rDr$?EwAJKl>*G$?jeU6ZCG@)jec|HvRw$-(y*nEbjgBC)RoOd=Na3B@GXdwOmks zB0A5@8DbCW1(YtQM~0o%WLZ6PoQ_9eLf(BAH6zJABgKe}u~PP15)V^&X$^h7DpRFw->Uk7wGmz><=f$sIq@M|Yjd*>%QiuTZ52KBBKQnz#?P>Y^LZxMpXX8)F?CV(G5#G~z_{*;YH|fzPhMver0c4E$Oe{jZeT?E zH*&QvqW+0-GDMrn_ILw#kAkKOHA6(TKQ3iByBj{0O<}L4u!ouK}MI|io95(b8 zQvtE~M!HY30?D|Ln{F&x?$*85Ot}f71DQF-Mga7sq4mr)#KLd-M*+t6Mh{ky<0;Xq zNTBT#NxssE(whn66}@MC+i#%xa|FP8p`Rvg)TnJRa> zvLHMIf26`e*%`|myab%31=qNUrQ}8H*>t9yT0%MX&MSar#M2xmw^HnswfxwW zP#Yl&#k#2Lc+th_1*~iQYde+l0h@YL&ljLUGvM$Oov2S0CxQiYb^7HGG%W<3`5QW; zuHI~-T}KbScM%;Cw9-}DZ6!Ik2g9vpBi(iM6-4klmJB-CuEfQ55IHQmD_~ISBCXs{ zR5ON}^(-Nz_nYotS6@#Yy@*+d5k7VL4$5ExK&&kND*r>rnx&2iO_T7w!FzH`8-K(I@N+&Zw z`;TfaKkyQ`zQ3 zltM*fMhm9#?A!J@yHsbI zeiNPYO$9p~WWD7>cF>tmc^U$fP!W&+uGu2dftIh}3%Z4p&NPDmOv&e+}KiGEPQS)3KcO&q`xWWjzaN z1?v{-DHM$~`0eZyVl*9}x2~jfXa09x?L$plcfCZ486%r;98DR<&{=`(&%A#tS$7j+ ziE6cBEd&k~sUQ>8y!^vtWBDqP;&%e5x-?HU$TQ0Q0MlGU@ez79&RwmmW4xCwulago z6eZ?R+@tX0D6H!14)l&$nVKnB|7az&CBs^JVT9pt5PC(fnIWShmx4}^MyKMSIGlh{ z*n@rZ9pr=eGJY+nyjt0NMWwXc=-U#iK8_g)cBU|fsW@ZKRw(Yo?xiUoX5ZS6?|N>0oDXI5hmU=&4wFuj&bXIte;#PT;J;s<0S-l&%L+$au(-M?Qn~*^Lb#AODCsMfV-8!nQlJldrK$ zz+KD-Rc$3pp9eo2d8P-b%L|}!(4>m=A?xB5ogQFhO{nf$+vga|E$ej6Kml1t4~U~9 zF!kraAuN4}kgfx-o-v%*z;lTF`us`|?699-0L?v<9}O6@6;YHbTjwD+a_fRHEXWVW=6sn4g0S4Mk$runr#NVjN*9vZCj zd?PLu7&!yj$~xJ=jqH^`h&osysjY$8ru{8)A;!>g$S2qg1r)=X52AQ}M>>;EQLoh2 zL~W3+M3pjTy_o%(#85psWo2nx)f`>|BBy&cmGq$dl!$9mQLGW z!UdV*$4l*5`q7nU0O>_v3}IoR2c?o-Lu6&ecE)Mq=PHAYC3%@<>9b5d>Z%K*vtQ-U zEg_}R@G}yssL)9jB2sZ;nvTxHIOiIYsX7*6Zw7sLA6p~QIrmR5kaFm$4%t#m zIPPVGZgbq54K&jf#vbDahOmnfOIb)$w2+O2N+JBEGUH}ZX-RuKiz$soOTlLdR@Ldv zMZU#mAo4r~qgcQQ{;L!+H-;a9#YPo=AA5@Qp(X)Z1a$rZwMHAsK51YUbB$aAS*21( zsA^Wx>XB7b=?bc<%25)|XiF(;s&X9-h|2G~0JmP0X8D;|9DX~Z?zkOM^A@|q$0oLF zZfpfsI{t*N;t%MWWp{g-0wd=c4RJ)%mEhk^sAhXJMMSJ+Vk|beD`AZLn=^M{apc$u zn?mX4r{8d_YHw*GdG4K&T%|2j(f72sN2vd_olD{#NJ`6`gL^UJ+pGtod_c0 ze?<^;8wg@R13~b6PmSIL%2@UkW82d|S&GevI%7o_5!E%XRhZrCfEoC$Lkg7jo)GJn z&g>21%Is1pwdd3~eQJ(#R4POIzY#>nZ3HoyVc1O7@7cvRdd>qo%|Zh8IUpA&>0+uy zJzEymQ$wj|?u1j#lt|!W(=_d~Jz&o*_ON<-A1kGY4*myeV$BbD@J@-ve3crR{Eqdi zk#$8FPAlT=l*{vB+|rG?ujNjK%QoqH>4~O5vXffPBIZt8u zy#J2j)scV1aH$2X+aTq_g?*aUzeS9Lg5`RYUg!RFG3;kTz-*cZlTtoZ6#3SFqsZ4- zUpkLwXJ6{^_Z39iq;tZteNyBe>Z~Y8;c0i=x!`vCPp9p&yya@3=}F*{uhgF$=_f52 zD&0_oG6h>65s-SW6=l`Z)nQvVsoB^%Q0YMb0^nSuN$TVLVx_Kw9k^;);s}((8y|^$ zU25NsiBuy9$5$ zjp@xifa{M#n`^@?T-!VlLYsIdM?Z+6X(Jf1jACiMHj>`KqRRx^J+K)sF<-Y4r_cTc zqM3@I1m|&&ibDWoX$$fsU+VQu`9y8}ijSrC>xz#Vz+xJh#@<#M%ifhcNoDY{WL{XP zSXvpzw$2rcv88XAtU`TfK-lPC_Fze%g$;i1xA!c`iF)wvl)%NJSC68~x%SR2{l`SF~;N!g1X7M;r# zdaFDOr4FfHH0rp&@?~ydBJ;-4V`x@psZI`f5R?D?7+&|FpWPpfMffp;%|kU4Qa{jj z>`z8m&53$M=gwdnf3Av;{4B+wA-^d*Pm1LXAoF|TL`>BiK)L1A@!IGQu}ek~ITa6m zlc{nlcS2(;0*|}vjCl+HUucAnWZEe6)2g>Z&v4z?-bLy!c3Rg=YAyGEts)mtv$5ym z9yX5|huCbs{oH0={1|WB+;Hz>#shc`BB4G)=zxC}dzkF4Ic09tAvdy*`v>VTHm7;6 zqw$0LoVx9_d2yA!BkZCVvpl4BF0@gz_Fh#-v$6H9ev1ieTEn2+qkZg_(cbCYZ8O;1 zFazUUNcB_oZyAmezB$+!oO{4b-1}fYyqGj!)f) zC$^~CCdC8%QiAU?EbPG2rx`M2(v~ZnpZtn&O~JTNcX59y@;v=)=T9&P;k5efmuleN zhMtG>^wTo;5F3}-Jh4st-8f`oB{#+;Z93)gHa%FnDwj@@TIUaZ@QTB&p&Fd+2tDou zGn{^5%3}eQx1SSdbI3G0oXX0dz!5=nov-xK!U8_jzN{PfxU~t?J{;k zlUuj5HO3wW0s7|sjmyDi*lUix z6cVQ}*PCs2^xdh+(zW!MQ*j*{jlz>PUMv!6>%>?)&#Yx}3Dzu_O(1%#$>F27Jd8(o zJY#d~*(!~hEL*BpyXu@#>|9Ot{^#ers^@p)<^k$7pED_E5&a@e=&TKxZL+A^j-~@2 z=jND6UA1k$!^LcNpb`jV6$_B6R927?3_2BVSbj#AG00i9VZj7*5#N|y%lsj}v5F}@ z*9*u6nT^Jfp2KXd=TPOBm2_k}nD}k6GxL5nYn#+(I7=BJ98Qg9NGGRsz0QIT>00C*Ww#j?oH;*LQf3wI`6=bOiurYpAU;od zNvwO=Y##E9Bwxj80sN_6#a3Y@o7Lx#m2H zJ~k7Wm1|940?{IkD@}+Hn51ve`|GgqwX~6b*#?|R`ZfcYiu6_77^J_WfossxwGkH- ze<3gp^cRg=ewXcDOJ7w&mor6?J|U`{qo*Pj(HW#~)S8m&eQVRJdMR$&ib(vM&Hb^4 z|Is~L7f=__@R^N2{j8jhhJ`F{aWGZ|)R%jxk7i{XTBTBCjW76$wYlz^So+wFuYRgl zhpIYuu_Db`%%5aN)k?bIMu!`p)-YHhMTS`yqq?Z5%5c73QyD;mv*^s@L%A6GCsZAE zFyiX+x&V(qrb04dJzJ$t-uVMERRL82H$UAX?%-u;_~yl}AcGdZ0Xz(v=m6Qbmldwhf4@v=asqs(s{Md zV|Er-!a-_uu*i$X9PtR|Ip?vby7$nh)>X;G#bLzc#q2-|?RsTvVt3*9I5( z%O3{y<{H})mwx2DrB}l+X~`VA?FWxLHN6PtGuzc~W?{ldtic;~Tb03PS)Z}x;kwQL zt@#D!%cjG!A}UW|dOupb4t{8_lA$!!lj;Vc&K8EUoV^fg$>7S#F3Op4qS6V=Mp{|D z`s=#ub`{~Jvbvb*Jd8!w@?##}OjW2YU_J7p0gM)7ERC`>#jr@NWWDR@cG^d4MvGy$ znM)f7v5HfDzUY7Bpzj@R6bJkXxuh#K)R)<8)yxTV`i3(phQgW#n~|bCi=C`v$#mY1 z+K_D=i3Uz) z0KZswQEEHKbZ_>bGL;54Bo$?;q#|~GnR#2+7k244la%H)Bq_}XpbkzyFU4qQ8J@lK zqw6M0|HQ1N`xI#pWL6Efb8uqmVx9$d4dl0@2gW{yLsmNe#EARO%d~7*3HVdWmplg) z-MJ=*nQRnWYwY~?MWMonOHbqECE}XW9ShSLu`FbmLw%zA68j^xB>z{NW`tNX_N{Em z=x9s6@e;*7zQoI3}7!kYNE_VJIkVc zl~8l`M@XQ^;* zJk^w9bZ^F(A6a`Gw-!V*4dmA|z-Me*AuZrM8n745^n;5B< zFFn|tvJbr&{cFAe{3GU24j-)8p2q@IUYXC7lhbcEb+vDaP8(x>Rqgj=L$^ zaI+-y1}hPaykGiNEv$V|I;&MF^QyZ?q2W4mHhx}xH$tJ@d(!+`n2q{AGsL7z#r629 z3&mh6sr0IpxeDG;HZ0dnS1lvA&{Z?*7wMMmNT#}vqGY_vMy+EZO*u7@3dXOh@-VnE z^_^=%N_KP0f7QOssAlg$Z*{ps>WY30V9?)OtI`To$<|140xgWGmPY1{%XA;$Rw{hu zDxh4(6joWz28d$C4kIO6Xgyhh@H~B`KiluJ8Evy=#iDTY!>u)RX_c!!HL@S0|B`7Q z${dnCOqD~H(HE#bYEeOgaL~=fY(=(na2RtJ%UQ=pc*s+ooOET<1L9=nMj5|$U?m@{ zR$b?ql2^^3JwE`VfFc@EWwKMd@m8G@wg)Yh+B2#=QiJ>=}Yva*J3i$6N;Kw| zKP-9R6^&K*en#SLwDGU!&Sg&7<|N33J`4uLq<<$xrn3z8D}5mEGgI$v*dL)~2kjyjDMZ~u?B zcaPJ#&j0`4?`vkRxy+2oVMdZ>kQ_roRvI(okfc(rq&`-%vbKZTR<^B}L9Mm3Rw}8L zq!X1SSxJmBgphJd6627>#27PU4%gxJecqq%_jS!+Y7e*H&mV7do!;-);rV*Ko`=Wt z@vQuHg>+{J%$@nD!fTp5E?dDc*;|(Zh&`?x-A@wSNd@lIh7J1>C9sLnR9MJ?FH*aA zKQBSr2Qsx>ZZJj;qwi-=*VVKt3^(yklqZ$$!iI%*vUs4ZrZRSMhx}bZtAXb+9LqG5 zjAeaBgw^t*J}JFUJjq<R;h0W4yU6ucP>U}E zI6X`ZW9y3BeQvdr1zt=!?rKVL=%2v{UkSJt*|)q@{?s;EQ4eaHh%3gjtOAdUtR;dl z^-ctfsKvZ)?}N?hjH8*bukwY_mc#;mN`N${CiRzHHd z3ct~_N3#Rj|1_Mq5reSqToL-+5Vv2A$J4s*D^8dGeP-Zo9nWQ=y89yUO;iu8I$+rY z@^{O6+099@70}=Y_5g)RT52J6$p}xVT3UE&g zEj0FLEoLOU!r^$T@j)E^22V8^9I}*r_@?{fmJYfXeaXcf-W~0{ryg**1o-h<9NAB* z4_hH8Z;v*fxe&=(mZ-nMr2`g)xrMhthp`K=wO+O)_Ex)(Ql$EsV`=f3)jpAIkgmnK zqAVHM`y2zXD-ou4hpv^&$;mDkuhk>N_nvXe!CBc_X}qg*i$3n9YcYGYW`sPfG5r2T zPstfXVw4v}V-pKd*WQn0CNn7!PHp}Owhw4ell+EiYHx@eNNDx z0(NGI3CzLSlTu8epy*Za`HLIb*SyN0Ot2+Zn)}(QFsV$~SVc{b%6Q_C*CnJ-CDYvq zBHKfNA^^5DA=xRU;e6|YiYm1y;%T9U=;}ngNG*;)k6Eq2Fa zaslgNia#mb`G^tk*y%~JwGin~+)oUr#XRFzR6ATK19tQDTcI{%%}R4( z|Cb(L$bXPj^FERHp{U|?tkL87DEmVue`T!c1p{OoZL!^0w-E3)_jKvvh4EJw#7}7d z%BWF7n#zyE`{ek}ozH+*w`Tp>vC!KkVHG#Z-`TnHjEdvL}R*b87!TlrP z{Gsfl*`r6ZVD3ClPk*>@!31-aOQNme#i$W-EDuWV@Kdk~VXZ&2!OA^RWvgT2$A$$Div9&GFRO876{^U%p zJFCu2EBENcDGih5D^Hz*do&kid<}_}4>g)9znR4u|J?CEOM3?r(zTMFO1?ZUu&X>t z|ML^ut+`f%uI6#!I!&#pAq1gwkM`hsD={^(;sw#4i2=&LC0tgc7-2eD4S^NM;;v5| zE5~W#NHMzXWsfeAi#kIugIw>jx4|r5MIEujcuX!+Zo^ps0)DUcHm;{0WFr{x(RQug zO3QqUvftgC*iqy}F4irK_N%bUnGbWJV_c1jLzy zB$`!MBNT8d)KQjKyw|a0a%c$HJ0pYDa(&|mrAL^;8*P!TPl#oF0{8va?McRbMkv~O zuY8$&g&^&_-dcrHPPCBB6W9-MRmdpURl*8@Wp(o>xJ`4K%X(AT*=hU+ z!MBNqp^BVUci{-2Un7)b;qX*Zl@E^`EW`Na%D%4C=P)L>y-cJoG>~YRl}HkZIx!nI z60xXgp5e^9oO+9oiYSjOc0vsU!tw2-**6)pXwzb24ICtvV;zeZQxYS?R2rLf>~?^d17Yqo|n%vA9zo>K2nYS;gP{%pQ60+-g|m` z7TDv|LJ7%kxlkfX;NwC6Z2*5LPFGjmLM+AgdQ$MikibN-@r|Cu(bV#J=-N6c5Ue}5 zNrj-}I?~GS8i8^07&U#Z`_g}F%;ZfAjn$EG=qJ=B?bxPFHeHwrDD7iS0Q>ZqR)@Q5 z#4R~)P4aacV71#c1DD~B%J=1|9R2$=&6-5O@6}FxDBY>vrss2Zjp}X2#2!0stV3F5 zdCC2NZayHh|Mju+qA(t{@F!1X|rio?`z-bbtbo=G5Il>Rz+p z!J9}h>U8DH4A`1v-g%hcCb|)}(^RrB-&I1Uvx0oa+wZ=ND$T9l!xZ!{z+fQCK{``>`YPFX@5v=85bDrA1wcg zmTcAacMTiCH5~|1F6&reuq(b{ecyAgzF&*(T`@SU#W;L1uIz^?Z|oBIVE(WBK$Y_5 zT%f8*c+z$&L#WDE$gDesBAn_HwU&$!-6B*4^g0IYfAHdC53xm%R2EQ1N9@@^5=bn6 z06k;@g=gPoov%PXXHiypR?+W`c?W{f{@Xq?)=+$oLnl|rY3Tc-ya#vE{q;u5R9;oQ zUb=lHcsXh!Tg{1B{jk3efUMD@!;31Bmf7k$}Gito!$FSZpmE|Liea?|9<-YhxUnc|_HBP0DddlwpN&o|_;!henHLDOWExL-29M zsw-TtoEeK6-qOQp73#k$iFzCTSEK znlxr>*Z63$)v{rQNf;n&{daIYd_x`Qu~1%pfa-cH>;WJ2mreWD7440AmJPBk$LLFd z@=J=S`!r+Uea4LMJVx&GEWWL0H|>@oJxXvh@#Hho3sI)M$E`1m1^ro4BMzntlq{U}HkUKLLSiG=3cgWYe6=rRMm9*vIol zQjWo*RFAmD5B)eu9vv-HT+vu;JFC~cxyGy=A&N@hVzDugk!y83Cvp6~GztecnriH3 zN*1qCxA!p@FNV_M(=opi1rg$8n6caxjQlcT9SauTJUU%TVnY_#oG zSOGXMy~lIVsjZ=JZ+AY6)Yb?TY+IzuKb1=>L`(g z?@D+9UL*__^CcorO-D_AUj2=hReP~U7bed zQHSwG|AnjF z66u@R>@yR}BbGOj8CeZ3HcQC>i*OW^bZpL$K+xc3ihtFgImK7GBXZ7RQrBT#>R`b^ zvh0A~9Ik%{0tN=tLza^vc3@{x0CN&%OFH4biPSC)Q7+irv~{Z-e8HHxOUqvnHL}yC zGW(Tc?FM!MFW&DQiYw$B7ZtrK-!QXR4(A*BGh!-}OfbIBz0NmE^qdqHB~N zO!{#Uv_x_L#n`og*rS!kaS0Z+@*jZ;@@sO+K2}EJ3e3hhiWQJJZFw1yGB`#QAXut? z5G@9wMWEqpE~A`+!x}a`UFM78L7^Z|#BAjJS_>0czUGHSIKfun6sux4wmFi42f)!N zl9|+_rLliXTbgV__t{&F>HNr1W4|aTn`!Zt{Pj=7VgKW@y;`@#$~kd$+M|wRnoHA2 z<25o`-FOgP7c|}zX?)txH=c;irgi5vzLz#$P9=L#Ud_gbS|0ziu{{i;Ok1{6xBc9- z^$t%P+uM25W{rZ|>S}yleQ}|D3!dp+XdQOTNp8PLQj#+o_*VCiTP52~8;`Pv_wLf+D#FJWo5%6yb907H)6VK3}M1E97 z$*3e%PvCZKGJ}F8H;X=KSCCZUq(Y?^&6Xo>n(XM^P~kFjDXFK#G}ayc^TPpy$XU=YB5Da(*kZ z^*z5%`I8B@TQW$;_a_>Ag@TmR-FB03YQ-oP7Y|vqa{4U-TVC!683TAZdc#3rcuhgi z9B>GxKS87Xn8F%XLq?d|)m8!?-ojZ=z?N}$)PEfEQtn9{wOWyqn26O7f~9yUHmw^( zbV7M}K0hX4WHp2XM8gs4`zxFaa`{L6*8dIFC73drYbB9~Nf)g62JoQVz)e3Ilq-kV zfxA7fBpoE7;s`#r0Nd~ZWW_8Mu@A+fAes zw_u3;`!+`Oln`OHUqj@R{&u`EaV#3q66C(C(VIq$%#4LleyvlUz!bxk>6BJepDtz$ zG_dC6v|b`sQLT=9^>61RrB}7;gZFbVy7W|8Xd8@QC#u^pIB(Kzg9)wSZmm{QonoUE zo7YaiRj%tar5nDt4a9vf1{YOCi7GE8!_;VLfI{SD^Q_0*c~NNUQJJX$Mhn`$Rd0A7 zw{BDCY2E)MX6Nibx&<|^(K@4NM%I<<2Ssz`BBCyD4DSZ-=*&GbHcK%*o}T!VC?Dwd zH~G?mCZV&z+f;>*y5aowPBwsb5fD}7^ic?qeh>pY$VHqdX91VI5RwJ4kevLXRcXL- zx=TOr?0yq~+yg7XepSgA(uUSIW+XK3za`HtjjB{>gI;yX5nmxWFCRjH`~^kEn!Dk+ zv0f#EDxV@Q`oY}X@y6WCJ}tLOF~O{qPjZsou^z#>F=@=B#$*9~n3p(&9b+EW;3Og_ zuqIgZ*p%mCCTfcF)^xgCMKIZh>WnYzk-LbGqbcHLx7FCYa_WIDe`CRN;Thz0Km&G^Idgo>rcK zzUdOGDj!|i!PrgYiF&bqXgMeR-k}>(+0~h6%4IkRAT8LYfL5DK5RjTmC*>eM?02w7 zVyG@+#XxPU8&r(i|I&6l0aVZFBoV{i@5pxF?D(H$Nfr5z$rQf!cj?9dwfDF_BX&Ll z8wkIq>Br*$?>>_hyK7bU(imZdMqUdI-tl~qv2Km9kmJD^AGkjY-wNDVAu#s4=E;y3 zgA99xE|kKm7tP3icSpH&3!~=S2zauvud&|TwMx4oi(q@nMtBQEc6Q>Lk0o1bH}~m+&_uCQU-zEh zHE^z1qI?QLSO@BjjAf_(K;B@NT3hml!(V5%g0G2A%63b+92h=Cx;bB7?yrLf=DW^> z0_|}jV}H7_y=a5q&ln>*}Qp6m;u*Qxq0|3d1FlyBs&0?IN}hG*kDMXuiX6%2*}P;kwG>tse!7Kf#!Le)gUXJ?L=`vOtDaupk4 zN2S2N{K(A^n#i7dIXks~^BStO%evSK6^61p zIDEtHv2M?WsGU-4Ld6MAL$Bu6Kc1=W?f%^S=68EY^`*2i!Q+?Vo+G5a6{_=?lz8jTcC zpfU5?Del3P$UL#M+_?6H=Vo6FierD%fl3fB*3W^w5UfTS*xHFSER`z z-V0CjW?A2f<>M;9PyRVr!udd*RDV~0S8r{f)CS*4DIZ)re5Y!#lZsReKxpvK0E7nr zY$4hU!ffST&nd*A5w)>{|yT(>^tsM7Xb+-1Q?5gEs{=*@l zGEAGZl}{(vh8{`{?5Z82<2V^FgwIqA#@CZURPE+GO48H@cPrnOQ`kPngzgFW-c#8v zgk&Px%E|F}txC$t*l~gfU8j)DJ%y>Rn zetq&4aRX(-&bv4|7ms*$hyy_@?3&_X-atsvfnpD%;p!w<$BBNpNYXm$B`R;&Zk&Ht5tpBwW*;vjjC z8G9vXAeS5EJzUPWb-7l#r{t19O@4b+PM+IRCUC2@sq}D-3GJTwjodqfh;Uan#IAXw zO=b|Q;9|Bw?%8!kxEmulLpkWZ@hgw8s6`Nn8V$ z!X;!i8;+A^_d5Z3Mn~Y|t?)VPMP8NvatRpy?`;&t578YQW6;Pel?|ze9dl3%qPqB_O#CPIhrZJT52$GJV z;Yf{g8gdpWsU)_GOp%TG8Iub+E9zs+5f~t?Avdp)rgq%#RyDO+;rrXGnNZtPDBRI@ zSlh7AemfJt7by8&Jp0>I0CSpmmZy95%g)>3ambV<`ZdL@oPCRoEX&%8$FXTHK0%sQ zh-XC;?-CuH6I(kxy@yu#8T3VzzsjM5<-BXGJ=sqVq@*GFD;%8;K1M5B~Tc48^mP_Zec+6_| zVOiR6ChXl6y?_socJCl}&(N;qIDHN81-}K9A^!^Kkd}_7=%}+7jLL)&PLxaNF&#&f z1wou257zm6LFhzvpDqxgHPtPuAwo?>IyxTBNHun{t}-3b3zk}D^?0~zZFpvQt)*o} z)>>v{t*r^KH3Gx`Zmo48H>y{{Rjjo)WUYbfkH-h&tTlxr>RKb^wf40(F|yVa$T{RT z6JBfKWv0DOl?JiO!b7wsGDMk?AquauyvQnB()@bfQGPDf{j<+8${KMl-yl@&G?k*Kk{G;@^I6*^vXbxm@`)m>fLHCI=5 zSl~ycD_q*`{<)&A>nc0NL?zw-cR4Wc3?z#jy>;YgR$pX2Og?Y@>w1`k{N8<8Y;DUi zrFu0^jI74&nyWF<*rCy~w$~2K)3sE>s63^bMR#3F7kob{XzEg&}R*v zGWM%WhKb1Wocah_rN$7-SHrx%#-V^Qr5ZkFq|21>n2R>u-@W%UF7wD4nA=VfHQj`Q zb=RIa=~VMU1tL(HL+cr9E=s|uLpK;}?VtiE6-1efTHt9Y8pgo>?~9GR?Iqc`+KPZ~ z8WPq9ZhvEs{Cw2BT))^~lSrEzK~0u>36*APloYSwoDse8a4(>E@UFidGxl}7t2XWL z-wCkEgsKi1J3so8Wa!PeO0(Pji zs}=mP;vWbr{^4w!3*d$K3@XqyLqNe+rer8#lrArZ_)Y-Vl^09i;K+{628t4}l^kEh z@qtly8+$T@sZPdfqE8lm0!XGhBoifOfh+YC46Wx?FuS{fCCpnVQ^e_^%%5kP$pQ&B zc}SGOJ&yyre>?M86UuEKxipT zq3ixTUFN`>FbHfpL1cVh7j?_dgj&{mXytGdOxc@|tlc)D%7UJ;R9F@T;sZ?cPuu;7 zuU*WSw~rDuajvUiN$Z^c65o^se=7m}P=$yJgDwUQ%yQ)~n2>>#Gnl9KIcu@N{NuKi zkE?jW|3di-{!(vk5YCkHQvVCdt;7s6=G%!?$>pUv|G|9&%e=KkRZ=Toh^_S@70ZIV zlHGPJ3%(FrUMfact2mLv%8Q1XsFdAWJSKh zdU|BQN`kQIBEbpGa4I!Od&a4*E3ZM$|CW~jVNd}GmU9om+crbf+dkNkR2NUV|8iqsOHSF>v)aFK0ikN0l#C*R^lwqK}<1kRg$+`+%>P zv4>|)qaJcAGT~^O%*BK-^|?}x-habX6WpwMKCj6J8JtO#U9)0^Y^;k}v;A=gE?Lnl z#&`}SJz?w-OIjp5?^?IsR_T0Gk+;ETZ~F<5^N1nQ1dcZx|Fh_Bd*YJ8$-OHwvh)U6 zHK1rJW$P%R%%ZvJ&<*~Vn7cAYemK`Kwt@I~qP&jUlkq)A1Otafb@ce2kb zr5m2^)QTaZ(x|ehEHU~oJnA`4t;33>YKoS~m{(J@*dx}zf={C|?~5=}U57rQ=FsQ( zY8(7iW!W5H?D&)w%9}%ldVP{_{Ax;w1u4aqt(mye{BIXYH~-e=tPItx{)@}I%A~Bg zUH%JBhT_h@i{Y%2!^-p0RI>1WZB83=`u^#%&qG&n?Cof@E7_New~0*>YTWpF6a1a> z!&RY{RY1U1*(Kc#aa4Bags5x}g154jCXEx&*lW9$PD82wW5zR=QeLKv>;qkv8fa?F z?o16*>!N9t?arCx73GIzV)^>5=H0%=N^UKyAOWGMuR6JXU7NE6;rW@vP6qp$lQ^vP zm6W*3Np!#4J+ve5fifaD)T*z5^yDVw;-Ogi+REBJtr*Glr}vvsK!Xi06P8^+=WW@x z*$R7(x?4JN-PqZ(z)F@=Pb+DLF$0J&rL)+VpPVV5|KsGX#xzT~QGWkhuN=dhiDUoq z*A>Qe)4=h@fg6mKlG7&{N|iUXScWk@I$bIgYkv@Tl?ff>x1l9d8bGT$Zj}i&9Jfk- zw*p^y-9}U8z9Wu~Fm?eTi~ch1vXW*@zYY5Rp`BaHcmw+;W@sM-^c`%p{uN`_RaVRA zKqg#G81uvQ2|0*?W?MAVIvl^>ut@PwqGxTEjrSc5`k*1qD)~6aN&PbKc9Us;m*l_~ z;l4DLMC;69>9Hu$oC#}s_#OlA$O7;H;eOH{?2z7 z6CYW3lz%CmzTejw7*+ARdb)VdKV6I`8@3Fyhm7zGl5e9mmoVUSO@};RFH+hlNN^Ldtl%7t7+{4-5KcVP(4;0f4 zD2&(7Mq{?K;iIyypR>(v%dbYK>$h_B8|3AC7aHrHrw^$AtP$TKhi*X1o9+*klc)Rg zbos%6f!p-@g)y68B)bOu>2ihamA@S$*P~D;DmDx^4?F;Lc6x@KV#+z#6uuP$!OUgT zFwrRB=)GGHOEYM$@mOV(l3T|Gl5*r)KA3Bw@$Bz(sqrjWIb0jq!?+9?@sQJaiH{{> z?FHWfy{?qyN~VPi^z8UmvOA#}7n8T(P_(EO|9g?3&>k%V5JbnUnvv#Kt|YSJaJvKv zS%tRsdmSDp7+-*6njprCUl~XVKKo~49#k~eYlm=Q=PEeAm22=U?0a=6sT3^8c-bYi zh}SJ;hAa}hIjMzOVMQ^0R`l%2T(TY9C*f0R?b;nnc6L71p3(7x-7%poexIy@}JJd`N}oatCgSOIQ;Q0d3V*wTaw$x8MAY#!-yH;+>LIaCQ{q*ndaUphn& z*ut`9aPny-R@u+dfZ#My3QAG{6`OBGW1ILv5=8evEXWAds?k+!OIl;KQt4zvtFo0R zC`_hcpzMY$LTUbat?^ni78(aj)!7LifLK@=$qN;z3*=~n7?y7q5!#dELq~+WEbYx< zHpwaE<(x$cSm4S4V$G55;uq&?giJ=9EsHAL5dDO?x4l{LvVG9N^FkhY{ZcFaoHU- zi-am_T+Nbd`g20}3C4zzi!V2_*~_%Tje!9!%JIPN_F5x`U>> ztXf19zcMYTPyETCxRe+9BrMS%BC zz(6k&tmy~MK|g?B{icfn+7EGh5#acpn1osgb1E5-K5zlYANf#j3T|Tq7>i$lz}usw zJ5;drE9s7A!Dxmn*kWreM@$3g@0*NF5JRHp-$nC#q(GJ_p`FH@BF=*A0r*-LSdPj> z&UEM8uwvPiwPbM|Fv$unDvcTRBb#JfM*SzS0xiIYm_jA$Mq$4g)33P_O4T29P@aU- zuPu=FZaC&{2PboTTRA;$+fgAGpex-dPuCVtLu1wHl=i>5W<4ko2G&7g0mLI6qQS9E{SAUV7)3ICHui>$!ek8*2pZYSK1p zuDiChLYCE-zYejIY$fFFWh=O|w;q8_jX9BXD}-p1o$L>DnmCVguN8^$BvSRMF?STS z7KLwOiF`%34BSVBz~#=6!g~lXmrcs}K|CDwK=Bm;qwu}4-k5DDJWV+Sk113Ym(q#5 zWWPTRPyE)+vET|^J&vm~RJfT1}N>Lz{k$>B73 z&La5=2&mHwwxzl@&k_#Z5>7YTTcaM_~sNVk0q()j@1eb3HjFC@@75qS# z0itD$c}OSFA*|YA-rf+*cS-(Pn zmq&;(=DBC{qU3WU^1qP#yJbtQkc3)60|U3j*7km_JP`v2dTV>Xwsxg(+nFu48&hA& z+nim00_SNVzz`=Ba{`H!tFR-vSbgxi%U>~(Q$lUzs-H>W)T+KUbQaD)vkKeJxe;Vy zt6Hv$(FgG*Tffhwc*DQJHu!lxM{g8qmi`s<^WT@t;O+vC$u?%qj>6=KO6I=sHSEylY9mn$~g*@ zaqT+2O9kl(;!g#^|ZW zqH)RpG!|GH&8jJcO(@tp5!W5y4OhQIB79R98MwUtM|EIZXu z%G&P0Dz#d$VV@$^K_Rx^?2U@aad#sNrY{a!a{_2rJ_qvU?B|V!_mo4EJjE8{*SU4j zJ z(O6BYQeE(8$2An~%%d%{XM%8g>of)XYtC2-*HWkkaC|yMiKRIFP3R8TX@exB!H7qE zkY_C=^+?1w$GB{rI0rr%^DjBkS@-Ys`JE^VkZzuqHDfj4uscoAE(l0L6Lz)k+nE); zn9Zu;y>p&%PI^;uoX$*`zgey?HI}2`sZhjB&2X%EaKI@s>=<tMoDbY}QkU4dt$ZcTmC3A=6`8N(aqAo?oYf2bD?(ZPENN}V^5^`tSQgi;ttqB z9K_B;c$|A0y5CpVbNQF}>APYpwuiJ)FI|h_L&zhYM?Z4zz( zGDCS&7_$Dd;F_YU>P7U^6sngi(*)9@eq25>NBToj@!bmevI;~$vqvy1Ivk;U-alnR z^M+o;Q=reEPkL*f94PXO;d?KRF`*I7wwut}m!DDPGd9OWW%-Xm+`#aL=FQ*2H`lxj zEd4v@VQck?yi8BgdwC-t*>m&NX*HyNlywrh-&-rwC*`3VBo@X{%H+_Xv}~r z48|z!J0X#%KcmamS>9Af{WOpIm2p`Nqb#wFL-*nxnW3a+%0J+kuq13a&?|j!UxpmK zfeM*H`z1`hu~XX<5v8Dq1@V0h3o|Ag+b)X7hSt90H}=i~vXamE2(Xo~lho-h+iZZ9 znuwO>?=FUHeUFH9ox+K3>QH?qAewE#DkzfkB^_p3s~+iI(JRvAbEM#`=ong;+MEhj zMgH6TDMuMRe#o3C5WiGnY~l1(_>s6b zR%ZPtW9j`L#!|Na@`YzBf2EtZ(bgQqkw5T}Oh2n{w!zRP`$(yw5F2^VsB+`pch!M6c6yp|2xP%I8ZHTZKIDUA~9Mc4$ zFHq@kqqxFv#USN832P8mou$(HsAAk8Y4zQ^Q`)ogof|hxo(13x?Qq|IJbp8n|u9 zxx?5|hp}T^Vr8j`YCdpdwYG~2?dWGN-DyskX0(~Fb9D|wO(;`vaP3GJ<0&ZrvYG<) zoN%PI7L0cq)Zk9zT}tH|sx8Mv*8DBiYHy!vUguP?^yZO!Yz_aD-X>Y!FxDC>!^rCh zsvyk0<&b8%>TR}Ch+Rm|C8~L@=#GKc7gV?`!a3RrV=}%viZ+?6jPFhwu%q0`7F$rz z`vLB{pIkN%zS3sRa&ttB%!EECgNh{byKsx5Kt3PzrTV$K^d9wvO~ni#fkh-(EtS8{LIliSVa}3F=>C;W5xBK{n(Zd_ zSJDjoRcTvjAzH0fXIfv4ValpQ9SzaXc#K8bW5tug9E0aPmc2iIpmNhH^!;N{CwAU| zO+;*V$b=l~Z9)e{3qTu{p5TD8ao`uB6~_?@D|v*(Y%MvaMZGvvoZ5dR`&m|oTIX=r z9Xxb{vH$v)Bd!)K{|@1EdK$NIxfW2Ew^lZ@DuV2l<@_0#$9mkuS}LDpi%M!9c=?|u z^pTilcEe*28T+QOFB!Z3u(tBaBieI^EgEM=q@bBXVvM!C@$+)cX>y;N9WeyCo^$L$ zFv3OFDH!B{VS!aH!zKgtBb-QP}uIf57!}4ao zN})uRH<6xvN8YUHoAVuc(-L|rZ}L|!N8XHoWx6A8PGKySx6r-bD_nUqXUBi)$eTF> z>lyOqP$)R^ziFK#Z>EBvNey{(YMhABiBXyk%`8G^4mMrm2%W;YRp=DrtwQIpbRcx5 zNOK|7MXBWP%B~O5E<$IFp7oy8l)cqL*F3xv_@0acQ`T&aBV$%ffKwKPkJp#}4ApWn zss+yq3!Z%w#Kj1nBZ8WvaE{$szLJA!B4VbetL%$me*j^LTYQ1&Bu zrVKS4!L#~_j%nRP5tk>B5d_c9;Mj8nFY46r6~^hxZZ}Rak9tB;M3jp8j(FBz* zL5?MmG5sAhD-B!0Z$GX7ViWpHzt>|N9y;>_>~{sjP>|Uv3Lxc_*M0Pw23Qtr8yc@m zpsF%Eh}?Hrnkm2EhRSvIL?{Pu`}%~jIs+`Z`blY}{Rer9{rJ%l@~yo<#DCM}YLRd0 zsPzOs(*ZalZd^Z^&=hri>*i0EN$LB+K^e!za*V#bZ@7s2JM#6r6n1akS(@i3ODwR9-mEhZE~wo9HN2No>MS<1PZhY8KyCFZm>lR9hn1D2dm(W_@^ zrsYGwQFe-itx(=mphH8Resn%FXW&K?`u5~=km2UuDT?9Ed*quS8-|fA8-<%ceHit9 zCyWx&`P=Yr#14)?ThnI(AxfDF3s-L`v=H&Lh-P0edWw}hB-$-rV#RAYUAE;0aL~uG z6FB(K$Jl44PU|k;Tu+tT06@bR8c!vzNV+fT*Y2+=D{zKQ{!9`P@-JaZ*fyhy$QH~& z?`-%^_Ou7(j9NPvsbgcumdA4*24BXktSGso^CXfJR1zn4&zjWl;yc898%AM3& zPki{$9`?(ZB^Pbo_LH&m@6S@TLXOAsfqyaPMpzkN+1=QLc9HiU2Q!V;t8y)YG2o5G zr2QpEs^d8UFx?|au-A%*X010p)`WH^4w2bKmIgux@msjZjBi;FP@;DQ!o~L7NL-4s z*BlvaLZ5Ei8wQweMM`%=BeR=bWNQ+%MP@4mN4#ORlS9qw0!GnM@4`S{m-t21y6Vk~ z_ZFhMTz^uU^$8B(J3A%0m+MhJuBTk_ovl#+`gU$DdV`h+0Fdgesl*$5X`^%8LKY@> z*AwV$U%HH5@^6omd20fbmUciZzbJo@u{KR+5a$h0&LRG)p{f4Rb!@& zS%xwvUF`n&ZnnbgtzVd^W|jM@t(sH71QvV34XJ_7rY1+tzG}o0T(;R6z)$-xnU&zB z`7>f*I}q94|GpnObU6yYC;qVi@qCP(KWL?f{f}cyA0`5KDZ(B&Dx=Z@YI5_h{K>|1 z1mld`#<-7kESmqQ{LdjWZ|>|fIZOi8hBDL5LXOg_1*zD^-t!;x@}ig9)8=gbq9wCs zKMob}!c`Md_sg=m+3hO0C-hy>`Qy@vLCG^P0efb-rOT)=Y^w6IWkd!?U*grj-In`T zsxm;Br3(0r-EP3ACh)fza8M6;|CI3@Xmi8__dum+4Uoel*n%S8P9!LZ+V>oavGofgKlmgpk9s}xvmL;pWp7Ylf~h(rr?>+aZ1xruAW!g@EbN0MNuB_>e}aQ7 z(4~v9Co6qf$sU=J z1d8cY5|gRd*283V*IlO*2_!3IP1l3)RGH%w@Lk)LqQ%*jCCjD9$vAbBIW3#-S>UmrqZI^CUrLVLyIbPLHJFJ>9<&I&c!+jsd4< znW@~IOGX@e*$^wmgqFe90qKn?CaKj*0Ebkez*84q|F#x4yTHnP0`~9AHE^*F#)*a}Hzb|?<1*`rVV}IIzu`KiJ6eE(v zi61ji8k~&k71czfs8LI*4m;Q65!bS7iL#&cq-<_g%P=z5DK4cDdr?`GPQ0<&DKTMA zP_WpX$fS7ai3Ms|X7WurL4zKPa+Jkj)up?lyzjOPWC-90|3_!(#4f_0G7jN!(t)#D zi85(5BO|GqsK8b$)XGMXt%dSf*ey1l)fwoF_*N1OJ#q7A;GASbIl&$Vx1K2^h-|GJ z*4FECPHNqoARR~^b{(Cq$Jx;cPDT%C#AGV8s;!hnQBrf=oVUGhDf2=$(9jihdjSEY z6KoB3`iBt6?!LO(blodA{8If`dnpFch@yT48)QRh1|aD;D63Xlmgy>L0a)!|KzR7= zD?}b1e?dkh_OQqZj^$k{6nUJ6#ibaL>ozQ8^g%gQ0n9rwZ-aBNnB)7>a>CaWZ>)2s z(-cn9DMlf^uE$X3oKp)TEh`yMlOhU};fk`7SEg|Vr>7M|l8!r5>=U~-F(IRx1)ahW z&q7hz-V;xYH_65Sqe`$gJKQaE9CYE{J7tS4$bV8Upm+DOT8~f1o$x0uK{=`w-5Mqb zraVk;khEXaD*J1K+QjIr5^E=Y28OJQ-gd1*tsgDp!YhQ9s!xQgR!;crw~w67@P1 zf}?n#pFt`&5EV-ytfkQ~W*`4c7^w`i@Ht3jD#tlUWl*wlOUNcfYjTmwPn}X5m~W;{ zItQstW^ZjsldntY^EhK zG2s!%TapBshVlEWk-iZeRBVm|zzT!7IKfV?9Bx8IWKhh;w!RSaOIB8*Y>+nQ@M(+- z$1*OOIYlwQnTkUO?wcFN{3>EYvDlaJ^qQDo{Z~8YS8Kv(MuL8!ib>KGY6tV%IUQOp zqPD66DgIh3RYFJQok+z-o7x`ni*3bU1C@60*Ddp^@z;y2YW#IXvXljpEU+5*>-wOm zM4WUqCb^>#hh|p_<-dz{?j6BjZ&(;(qVh)0kbbmvcDW{8T2{+Sp8Hj^Pf<3x*=~zX zsgr-+bS-k`as8WBDvDY#ra;GlkQ)A)DtNGi*rk<#E3G!{zonYtK0v8J^g;Shhex4f z`ATm90^3A`Fc zeOc9xB-!<5PmhUU`}}US9&lY4YiEbC=`~S$VZ@!?5Ld`47*3R?TqR!d{!V&A95ssK z!7C-;k9S&7Pt~*XyqIvin8~ z@x7`6@!<}$?xS?MgRxXWDN)`BHY;n7k1 zKe}BktBC!O9=KbGiYbDtRZA5tU7bjvTal(3s64R%0?wvo#o+RZ?v}~E#^wfLL^Rm$ zm_0TidcLgXxOXDv@fWcC!W8 zVBJOIY{S$QqjqV{MT4H_-&eh8@*;~SFT7|vSdm53&s{V*&c5R=7%hAmZPm7D0L+x3 z`ot|uccrc7qS0f^#=^X)Ctl@%hg6*Ga~DmcsqUgVi%Z|DIF>N1^*>!S;Vs;b>-V|| zRRE=Q77Y-8XL^{ZUs^N)E3#-fBArF!ZY6Gdi5)xcBNGbr80z9-)exZZTMtt^hEe%)Y<~h$>?Uo zWQMb0^kf_uIT@EJjIp!)Yp(lMa^=PKQ$gJo-#(M9Iu_WYP7oT$m0wz)>>u+%Lp@33BT3 z?1+*{YsbZlL9$`07@5-W&9ZbykBg}xlg25U zA4eIH<0#Kjvz_B88lN1)_;x9j6c}Y>Ao9*y#MqDfHaUJB_`e<$hT^S%rTv*5aw{Wi3r<0Fj|BcAB)~ zFY*<01FkK5fA>@|%+yEFZSy}IO)oThkuR{zguZ<0l>X=<`QO+t4=SkGC8L)=LuTxw z5*gjTiuF1RJFgEigaE9hiJG2E&2}GQ5i5pxgRfq;Qo&!#yBsD>*!AlSOQ-#8pp#_X zm}%4zvn*D**_ZBEHjb;y8Mr+RmygW<@?+dDdXdboe>!G7fbXit6WyJsN;9n(vMRCY zO%TH2s%ER@%PfAs?iS@xBkO(S#f1g-IUx~1kCo%ztFgVW= zE!2LRDkfk1DI*ea5>~A!FW?jmnL2UE_P;KNx=tC1l^w&RGAxGm|2n z$^H4xSpRWnqFnL$<<10usWUgjmD~?wD>-q9d(=)P1L2eW8OCZ&1g$&q^+!odyoHF! zMo)sS3K1ea-~D=mEIzx?c6ikP10lLD%)Q=Za*p$@+Jq>>)g`SC{8f}uLx>Vh)HC?Y z;^yV5s@9H6w21h}vnGLiq2OL9{?`??GtvZi{R|^bQtga1#?Dx9I>(_KSMmw72JrM+ z>3k?OdUQ@sFi9G`X~4?z7ph$@;A1+rJYeh=_1-X6)#UBQ+F$aqu{$LW$w3Lie^Uo$ z8V6g9Vye4|$=PwnSmP)up(%kV43Q&de17@EQtXpcy_{sd0NZl;whhF>w|&MHud}AbSE6Tn+1cDjSDpW8LXAknV?9T~$3vNcwbku>`#&165?kKJBVce*ghU-jpq|jJ2Y+paxNSL&iyJ{z@d+#=8!CRql-vNM1-&158IQmx^ zob4DYmx%su4x>l6R)UqGX@4;0Y6`+xF+>oG7@a7EjZ(kXoWvJrib3TEG2DxZU@4L3 znVd9f{aY}Mx7B(>m$ldd%nj-4ND; ziZs>og%BDvTb2=4f?iwESzh0D)>zHaKl;A0%2+LUIAt^}UrP~*dgza_kF1xb0XR!k z&_F9(_}enkNq!_@;S{-kC)i5L`ioSa!d+bsa^~x;2tJ7hg}?(!vDQdgFfV|bd-zB* z3g@y*ERoBl`*1~L=eI>f?V|n<8PD=Lw-J9@^|Wi#_kiDT!~LWQexlX6KOu~>;r^HP zUEchyd)YvHd(50GzXqjzJsNe7S+8)B)qef@Nu(G(X!Ikj>qPZoqwtqM~JYOnFMaCbYQ) zvduJTXa81jq|9v<03fZESuuNxTWqspJRXL?djOgOus~QjGS2}S7t4k=rHmZRD5SQ$ zL`L10_mop4fgEYM`(Cv4H%Vdq7!}~w&Zye^@g23odv*d(S7Xk!Gn%|!4`VbF^`iZ| z9@YTwdiZ8JhPV&3R6r}8fu{-J6pG7(vJ$sy{N{XR^F!GJOY+#jjmF9XabkHzpR&9F zGkwcM+@tUj)oHDnVoM2iTk&MLG(=yoi_^FXLRy#T{G~dx!>$ntBBIFLYVho8zly}I0+UFz8@iQd;iADpYGep8#!UCDj zlB+So2x*=6k=pgoU%g;L|Jnmy;)?f06~FI6Sq~men7@8iB0ofP>dF4IX~3uQLl(kc zs8a8j)74C)$oEbpXYr-1 zG=A8>?4H>!6RorS{sQ+$AH1A5YUPy$=@ana)S*UCG`wYX8clE@vrffE97onow36%1q$CA{BO(R0%JYM?tC@hYmF>i&dhduze?tP<(L(h09_i;MRO|Dpe(Ws~s-J!T zG1ZSTUy-WFB*myFE-xVqBGH^LtSvmQ}GU;bPGBib! zjsjF|O4#-2o{QzYAUwYBaRR8&+{)xD&jZPxhx$B4`nbr__bo*fPNM#N&yI5p@AZ+V z{PGo`J%^e0Vj_EbDc7u!VSNZ~raSO969k1;%2}iC(^Djkn;(^a@uRpjuj_oi0%uGV z1t*(`DyQnIm8ildQf(#TN>CqNL$a`&@bY5PGcJ*(Vx>GOzi2RAwt|ZRbHtV9Q-n%> z8+UTDTaGFQaca03XTrshJ6M;pF;`#=B$Jg@j75~Fo#6qAW6{10(n)@o z@}6Aolnu23SkL-kZxZCVuo8%@cii>8h`t2sF|&4@5s`R0X{=mGhyF$mz|c3F{$&NwVi3#JF;$v(TkAez)MD|s?IaOv;sP8)gQ++~v&oucWn6< z;BU|(+Mob^1FEGpo8sQ-#7&ONxEJA@jVh6IE|{0|j0yhh$ewD_=}KAn#pKR!!{I_c zvw7Xu&SB^<>sS|QWSBQB5%cWcQ9YywK;*AvW20i9sVvNL3l^Q>m`jCd z-%{B63erjTQHiN8<&;m+)96Jo(Imv4Hg=&FA8f)mFP*!AoD=CxlksAeD)>i!N;*elN}PMNlry_^J81k_g_B`d;Nj+GFk^=epgd8K%EI? zR5SR3LU>I>fD?kay@G_nE}>#VsTlFfh+t0Ap;UC2g9^`0f&W2szw4tdADG%to{F`8 z6gQ`b)4_vt%+r>?I$JL5x;<2VNvYK8Gs_*oOjOvHG<)Wlh%c%A(0bvZ#CKt|TEwB0 z;|q=pSFh23=aE=fE{l*F)Nn2Z^d%_`)*E}y^)9gp)w|S7y-UktWNSHegNb@^Z&PVv zvrV$A9DIJJlLBlGXn+hl@$a;X+q};}^S2Qx+aNQx-{S?cpQ(j7gq~;^EFvxbJG%&$*jq_L9SQ(X_JWgeIQ$?^P27i<0kHpdoj1ntuYHzo?Y8{aM*7G8P zHfXpWvISbJaztB?N5|KWl+`+^u_hj_-Y$fYbtMd~vzlt10sOz3Lsix`|+qxV?jnd(vIsh*0BC zh--s#$a>UpYkhUoVtLocEmX!DZGdr8NGef7DmoUCz+JDg2z=4ws5m;u-qp~neaNOOOIn*#QPWajcqNRt{5R209{-Q~>HDm$}Sr}=^4jLk3G3cGc zG~{Kf?BrE9g#5x*AbXvLaJbSC|<+HN>qg{JR%|xIE~FisCYOFV!U@sL~{CX-Y90XC?M9*$rZ*Lg2LRNw2%AijTGU$s`xV* z<|`+Q(t4WntvN>g4~H^Eveu6tO#*9T6p`O5_sI-(PXL%@T z3d9l^blR+bi<3-X;I?f}@!43hzJ2{%w_O(suwL~jXDaB5E1xGpD`qhe+F-%8@&?<& zRw^`Hh3)vSa%GHIHoOI8tz<&KK#kt94;096J5P^T| z!9U5?hPKovQFiX3$Foz7JptBy>;G6i{rmhCUL-PE=H|(m#Lj|uYDi!<0$2zjuvXY#KYp~?pChCY?ixBO_V{|2gxwTS9q++z(!E7P3W;YGpj^AkG znC#SwBxA1Es8;>Ou{AP;lyJ|OCtzCA8;1_{5mom=+{NoKGoSW#=Eg<26WZ4-K^Uwy zY0gdb_Be^&fux!oeis^eHF8)|%exk;OHzJuMMwR9n8gB(SD7~5e}A2L?%=+QME;t7-F zYl|1#Daz4!w-VQ1e&;DQ{UWIaVP0>i`rsR@!g&a|AIRJ{q29q#nZ28dQms^mhdOuS z7lAE13V&!ZNXWBTP8zAy_$rlKh=VxASiV1u{ z@Yg}MoxmVIh4HHwc_}OSqJbM@9r(Q7(40brP9Bjd^744uI+2%Tj$gGkj*gk6xTI7) z@xtrA3NbHbJsu^jQb@sdEh+jPY;=y&PHM#E!}_FF>TN^2l zRSLJ0ZM*40k2_wf@dhcw$WT~a z5=>t)8n)GcHD}wAA=0k4FTbAY9u)?N)~9Yt{W}Ai0259_kGfimL1;$IDIz+EqKML3 zS?!(rFZOct&)ml#O2}i5$|5cWoUChRO)6g&fdtS2M*@cRNl7#wDgoQ=(2GR?202m? zoHhMc%?(tfuVsgGGVchg`zgv->&|67VBab_>l^ESX@P8-AZ`BB1)JpW_r= zjiVzCD^29ajr`Xpbh$NzF6zR_|Ag>UEGzT8$IIBiH-DP(%o_7YP5g?F2!#Dh0#TDB zC9vyUX*kzZjz|L>j5Vd<0zr^v&t`%gP*uT^0|HRLC$Hto?aJz10Wo`Cir3%5!Q5|8j+3 zk-!l-eU-e=sfWa6g>>{2GS+oC{@8zkShX`}$UVhkv~Iz$QT?wWiyWV!bqU@tMIqM3 zU3W3i_IhZOt%#kZZeBUvSe=y$2YWdQCqen!G79xH?|rAnxnw9Bpc^$LNZyp)sd)>q zjU;2YCp4w}+C-+wB9g=Y$|~ssexdrL!&~0xW&SRT>+5C{U6x1+KX9Q&xJpCp3w^;A zZF%=1V|9Y@V97}^hZ|eBm+N2QNHB(i^NZH<{E@*qNi2!+=wI1BsJGLZF@zqLS<43S=9>JGY`TOchksgWxb4C1t4j}s!^1%mwmO{ zpwGlkz*Tw)GBNq9qaq?h>x&5ndHwUU$yoQFoM|iyP+IZKe3P^ZI9TMjQIl*{M{?f7 ztyLnDQjE&uBhIJnPffv8d`a!CQ+U)L&3jhbLK%U#!0woU=rE>iCn^kq#L}Y^W~!1Q z1Er|_L}^eRPVg$qsmDWIXeHqDvA!Jip)0Dlt{}HJK0pAc``3odMv{goRkt+>uqjUM zV;qx3n07z3Tt|GY3L~0Ok4&R{^XwGqh|PqZEmNdFo9J1*#xeX=k(BkkOEsWuI);<& z^39^j|3m>y_4$7=sYM zxQC-p`FAgRA6^g5O#&`*9Del>Tfld`m)8`U22JSE%%%c#veRuymL}p`u|C_W#ttH0 zI*nFs;l4FHMJO+(ZO6&1_LG8moCQ)MR-oB9OBTpI{pU;DAbx*;hsiOaGtWPUfzMKk zef(4D5B{|VM8pcH5Z#zG$owU50MC*;PKVsT&IEO|R(#aO*tu`)QGrb0a(Rr>JjmnH zNpU08;V3U!%8j^T<+e2h%OEJGe5tAM0R=CAIa{w2-D;MVglfH0>9m4%I*3tcLd_#U z;d3gtSq_y840)RM7@>IympOx*Hhtz8V{A`CtaFTn@|1?dK<1>piNyf@p{_HhhWF8X zJ-Wn=Jh?}|8bXl@Uvjh^Ai;eX!oRaUFo=>|8Ql^{77Ga)7}zDWqLe!#GU*A_Lp5Zd zCK26C& zXZP=VtqJvU?Ok^#f+X z@eIu2{%p-@fsAlix_m#DI;~pvOlgknbjKZ1Z?a2ZiTY_JqF&UW56PL`>(C8ehvlJ~ z?1{M%-9L{o;=IRq$BLCVFu8MF`L2qji5Vo_uhR86DoyBKk|WFvIbxH9G!d1G zO43~=Gp>oDgd`+{f-irOnzIA=$rD&vsLy;O(>|8At65y_=GuP}Tr1l7KJBKyq5XW;do}546 zYe1gJu>>=;=5d$keDy?;ycaF%ntIOy?@H?3agYtX#9IQH`VBersoGm zO2=u@Xr}aj%hoP7>4F^w`!YmnL)7ix_NZ~&zUW9c^9b2e_+=y*# zCn3a5k?+h(g&$JWg(kN|0_Y=29*x~2n=n@!DfY;Ww`IJ_&qFn7*Y!EZzm<%XM_#yk zW0I?yEY+eV?rbfJ%oehT&X&2S@@&PNsve(E)WSG3-Pz)5x8I|)Riv}k&K@JP1s*~> z{Fb%4E%6y|Q+Kvv%zuupV7BgRXhIfhrzv=FPWvmOvz70+0O@iRsn+Zys;tpW8R#uY z9~QS?F|l<~tE;(`1 zS_UgGF=wVunpHMWQRGjozgfv@%iM)$*tr8nTa%U=k)hED7(6xUx${@C+9*N11*j{V zu(QQU7E;KZ)u2kQh2w*VxSYi#2v2`9`*VA3{ zIX(kaoU=D)wHz@A@1H2!)JH19#tZv@gu)cfNw)kN0<1^g!LQ(2q zGluA-7<}9fsnw+bJwU?0x&F;HSc&XW=P)Mn$=f;vgyB5g2v>D=#Y-dU)QS55a7e&6 zG^fX_SaTFXx)3K}H^g9P%HwazF%La*A*pZwJSMaiLII9p&~43fM&&qBa4}Ru0MFEM zfa@HB*6IUpZ8NbH)dGaC<2x3zSZs(#I<&B@Z171`gK0q!oagA#R>lkrJ&}xh zFXXVNyC{sQ#0h-z#;-2LzSb4&nHdQGe_~TuIgbQM(D!!}tTXh2O<+*A7ESo-uY)4* zOvc9Lv_99y5Sv}d&Bb7*D!g%ULazTIzs;~yNMRE|vLX=Q>_jcNi1Hby;cyz4@t8D* zX}-$`WOEEMY$AckW^@L{!WNIzR5yE+0YNi}qwyN5D2ot<@uGPEViWc7p_V5oVk*rF z>T`JBSn3AzrEAr9RJbz`-;)S7I&&MCx=tJ=uM#j@X_=8~tg|pS`fBl-R*zk!ng-?~ zG9~QG9pRSX3M;hdOwswav?a?xHgGML;4}n;FI@UHqSO^sOCxEdes;ti0B^3vp&E!- zOuthXrg|JXzZPr6ke}kfA`Xk%0L-)eNw!vfRb@fiX^OIyu}!{Pe;04oqKu8<^o{(Re0y zec6Nud@7>cvnnnBhW1vM5LGW?Xd_%}XA_H73*9T`t67JQXEN{;vJ^*9CwUP8r!rQa zuTXr?$bl`#tU2zl1+PvK5T#zjb|$JrjsSmlb8 zqOm8%mXI}owMev2Jo&aj8$b=R>86t1EJ}QwO-K-RfN=507sw>zP@r;vQ>QH&VDm%M z6RO&os}z09dYmM@{+;tM+*}d0y-1^Kd`yIb_`XwHxzwIq5wQqe@hrrT>m>41yr`&7 zbq1L}0SMsNQXhVsX>CG;4_V6+#<+Tn4!p>z!La=~k+uB?n55;ubslZPo%^;{FIkh$qZFgNIpD^^9{IM`OspW>YzTlx~(^ssX$k zKmQivx$KOILW;*{EHs|MRFgGD-M)hqVRx{@a;!MTSZ_-wXM^{)puY(()5_2XWLq>v z7&(fNDfSJZzSrJuUGxZA3an)x*v@sxf-3@H&r=Gvygu(F@~EOw)F9(l3z9?zh}vD% z?~!rv&dXqc;DlHv86ayUtVz&eveqmG67W~8*OQSm3LbnBO z>y(kvKR%hq;y%IDSIXA2wL96njYD2qH;(I2-FR!7LM)zfW7 z?<4O?D`Wr`*L(I& zL*L-8eXW@W?{KOUOBf-i&QWsGV8+Ln!eu%^PE@{%7oFYZJ;ig~wQY{=s~{)!kaSCjPLIIyD6c9J#GTT*ugt0lj{c|_x&XW zf6Y5YRv8wdR7FuyN=4*RN{vhG7@<@xBvl=tIj1@(D)0^`5mMFF={cWBoKtv*FvjTn z>AHQthWGLQ8nKy{cSKe~%!h}jyDOn4)44LT9HJ%1-1QLpuj~_^pJLA>Gz;V=Wm%q zmrBebvIkiTK5@vs+djWC!Vqg?mRH+s;&{?+h}NW{uk49hlOAnl2aPN0-Dpk(>6G9wbpV^4c$&!|^@TpIs0dOSX~0&# zs{(1DDtEG$12F)LW-J~aUlsGHia`n^Ea*5aFiD?E_+OrC-zmX(93k8C*JJC!cpT?U zzlli=?lEC$2I|c=P}i$)omAyAk;XUfI4n3x3%w;&g`I!GQ*PtzQ*z$^4jIJKHpY`5 z++U@~JoSXiv=wsWsw#cvsn=F%bWOQdJ{5ar@`-*Ln>yjwuo&N}Dy?!ApHV`Je9K*w z`mt!Jo(s1fP6_iUG1$W|$5plo>6C4i>nwNq2cTa=sr2Q(r=%rQ zQ-u5V+I6q7A-nAZYvfZ;;c-~_Y$Ib9fdRA}m3K9+yA8yn{vt}b*-0Sb-WD|MmyY-o zzFRmnVj>Gq#n`kM?GaP1Vv2-k0JZ86ygO6DjP=gx=so!(&ycUdTHY8q*S#=c9#oO`JPwlkktUq$)zz!%IK;cK3ISiYV<#FAaoc?5j-43xs%=&$v# z!~cSS-2~}s`59TFEK`Z=bFOt)q>Rbw={^RfwTuZcWpBu~z@I5Z7#t77D?24UC(8#_ zJ`I&OqggoBI4%0nnmwT<`$fx9qS+dM!Sd5#PmU1lHxy?ad-Ycpo9zWMXKiK$zBJB7#4*N$2xqkoXe#p<&CyaTPpVZL#59-F6Q3UO`8z%#>Vtqiyi`DWy z7UT1s~D7jBYt-+n;c#~!5+fBe!MFF8%!WQcd zQTPV9MI{OLq$cEl)hi-9c6$@qv20R_e79<3Y|Ts-<)bn(w*HBI zu!_v&Xa;kNDbMhj`mXp(+OhK;h%)Wm%w(ik%~B@u*d8h+Ek}N+l(c+_tY4eCEPu)% zZ|@-sEOl;X$ug1M#cw?QsoFJ$q}4GUpHO_ZxF|kbB@i-CG@5OQ4S92X z>R|L~E`twiL5!jfv<$o1!vtGK<196LF0LY+k`bs10Id=>T%~Wj!Im1vXS2r7OSEa; zZ4-fhZ)?26do`y5I{1|~mQx*{gxFQY@dbNU4&och0Y*lIMir5xRRr;AbKLmB;StP6 ztzk}_qVqHr;3ki5=XbaFNU)+)Rj{H~U$Q<9+2uWbpPc8f)XS4TRv8KJZ=u|6+WfQ# z(VaxFClRL-tP=5xPg5(#^EYyox8D()Y^zj6!sSmCiRVW4W$9?D?e$49yPp4FV|Gu% zd;DKxc7=?3#8w3#P-!t&wdnsAjv&RvEb`W<@2=QOVm6+*Vz;nZ(<)Z1%L7)Bl4W@S zicug@_M7iYk}5briV3I8nyd4oVI>VC^Kxe}vXoWit}L+WqL-QR%hZg!$&x11b5gwX zaDN3duS?`ij~#wl82(CA6PYSm75QecbH^)}yz!^LrxK_(t2S@gznH_6nP$%qD> z&z4@lF-XLTCq!~F%d~`Me&mkJ_;C~5ZOF+m3t3tPPt*A{Ssp}t_DH%<`qJXibdE^e z4KSbgIAfwLB$eQb1DGBHk1w(w4O5bYFa3ZPL_dtf$AM7xk`op~YV4ngxx2_QeyPSa zKzwaI*P^euo>oQpU89J?jObW{w^KG=Hj0e;&&xyYdn=kHz8CAg;foqMX$IjJ?{;BB zp%Q>Lp3>qk&oL&>dJt50kuUoGp?hT93Rf%G#IVN8M00kb_%38xj+oHWoAYQ;P`_%#FA z=lZpVZcN0+fZOkMa#w3&R7eKMC7IBz>hajEF>1y#KZ#UD_A2a0VmK!t;r9@7?innLQsk z?g?k(pm(afrT9XcOEf>zxWLbk;ws#Ar3g1TSC1XT32>BcKeQMpGQ6)LVrEMqGgGSCWPmbYOpGZ3Ha7u)-Eb-EDG#VC4QCzBero^O z42Rj#OH`R1h_{=8G>aRqA+I+hU(7*q$f4%H zguc956L768vsz_sG$Js=CVVz{|62b;(!_A6$SOv6-YyKyWA4R5^u?_u@r03k^q0F8 z70$1SaHQ|^gl$fS*ID)c`*5R)>FILAb%h0idL{KZTk8ZtiTvck1|~E;RFuqWH@p3^ zAZ&~(!oHWXPsa)6J_{hHy^j+XX|9_34vG!wxT&~KmUBbzBMVJYlFj7`5|)c>*%#Bx%n#| zyV5=ApOI^+{_=VNLsA5|`|>4jvg;k-*l{rP?GG|Av*cGfEOzr|^)So+u-Lt2zWZ3} zsX};R&sgIfa#xZH;VszK>qiBQ;l@<1BQn$*70gIvut4045UKJN)jW)tp*3^Dc;-NT zG+`=g>fso(pbxq_f=U}yKQ#Bg_OWrMXJu8$Af+cu&(W+K&x!Jff_#qZK36#P{98`Z zG2SE;TDu+X?(D*yf-i(a-lTl?6t!5sFh;=P<(T`QVNtreP1UTv|n-E_WxnGEaxfL8mAME z?VMfM?(Ti~Y6RN8qCSoh>CPHS`!rtWi-gNj4hh&7vjgk<4iuL%O)XWN$zvN6$!!pd z{-Q6%Tt6HQI^SDI<{9V8Q9?1F;8DKi`h4kAqbI+Kcgfhb{Dw@V^q?YySo2M!{-T@P zymMSvp)`N|E$;ZehM3;J{T(s}Ll-`S9zd;*%vRFgyFgL819NC2lHRJ<7rywh^wh~E zYWexSgN!r(z8m~ng;b9J6v@kc3K&95hGX=EzF-F5)Wn3!fpB+x_44rWjf}Gx%jWnD zfPRa14YMgUWbFF%F=K|=?HXh$vj+J}k^WtzY2(682NYa#G+3j-aaWL?ya;UTp*B1D zWT0*r?;3mnh9wBq$Ik2Qz6a=UzPOom0hMmGuCyXOSENFZMeWL7_)MIe^ixd}`f4}G zb)mZPiOTVG(RAnk4$mw_6w+@t zHx-H?4Cnf{!}_HWZk%1Mfy3Cq3o3odu0P{l;A^c*@F{M1O_yL2OK=KH@I@JW1yX$Y zYU-p8oH)RkIkOJQ@;kR~#q#?Chn?b@R5pcG_;XIp%{R`~B<4GhJlvmkw^-L5%kK76 z)}82!vhHR_)?Lbc;5QlUKgedX>+al&b%*NYuDk1^>#jG#$h|C=JPx|lR&RHCbl=VwqjfGTZyFie>ik3Cpa}@mOO#@qfzn4$KPw zlT^SNU(`|pYYKd|N|uVK5LhoVSa6TugmPlftQzLMyr^pDYuR7Gyq6T2@Y{nnU`!Xb zeCm%YFEXJSW`A-?QOT#lqCin_Qasg6pI%b>Fw0#l`e@AuP+meK zF$RQMIKJ{p5t(}>6R`^$$TAKqK>|@IdIg)-f0m)#UZy}IZR0b1C6Q+pH_K}3ULw=9 za*1wl2%1hc)LSpVR-2IP7L`*}R1cY$9&D2elCF?^_D>?+>Je$!5O+`W_>4T`G^4(7 zskF#xp(WkVd<{HLf+1kGwnVrWx++7^bACYh19M!p!qUycmCGL=lRZ(HUo{T3ps{9{ z=8h4e;be(wavW|S*2493=QT0m*+gW{6B72^G3&5On0{A>3>7GX_i69$vbbm#`VtgK zA^Mf_M7W3WsqlMRNV^ojXIUY7J4#fDV8;?IDHu6sx+7MK+cGX)8W8RwJj!$1+dE~^ zwHH3YkSM^Ht!>~3qZpi>D@jXUPH0to zwCgYFFNUNx72?By+7q&71RGV$3jIG#81jqCKD>ly(a&ugxLWAE0yN{0fTwr&lOgw%+! znA;Xn3$RSU%xq1ff3Q_=;7~aA9pelJZD#`oT;%wo99K;FmoXzL*YOWFnDZ%npgwGq z5|QY_R1$Ik7BuzMVAe6$?WO`un@AIFZ5@Z?>u`T=`AJ?Az9!pcXleQ$_jX$P5QHIzm=s|dbx?|J;#?EXu|@n8K|+(@As9v zn2N#J7Qr?t!8Y>NWp^a)6Kc8RG|u1ND#x`CKWEH-X8%`WO#W=&QF_p~@v^@RNHr09Gi?Z}&VbXT-;^if=E%7+G^~XjJEy zYK>k6TEiwnQuoMSYW}PzTp>JUR10a~IjHM(un}EwpNwJ6#UiL)G7rPrGjoaUx%TXN zU=4n4oGt_s?;=(GlKXDJIK7Mt)~GW$Bs!^O`Je5s#bz}_-q}2EfpOBX9G^ZpmzaS2 z3PzN^l?=G!1lbw={!p8;dVYV2?8k9b88Z9GCaa@B(`yI|8J966d0&f?0fZz*cireN zooXSO{)f=1HdXK5(lR2KVy@qqSW$!}P8zW!@eY@k)?% zsBTcgO7_iIL^?(RX&d-|VrPDX6MDXErJg$6se$}z$bN&}U~b`R<1D)Mm~noXGLc*y ztofu{w4?%E%T4vAIiwADNW92ZpE|9e=58l^6nogqFAIs>zK-%OEh!`N11}^O$b6qC zeMx4oUabCw9|wLX#|6GiMZo&_deLRbugGseb%Ay8k{>Bb@*~PmOHD_fX{1$mP5ngZ z^eTCKZG@KJW~hnMn4zmI)U=vXHT?93&SRuqH!tijV}~O!6Dg+vNV*IZ+YGbcI6Y*S zbuNbiUi&sVP&Z!4ZkM??OJ*JH%F7z%-)x+xsjBYp@PM?o%gl|Uc`${3R#iDcm*p*2 zoA7HXUz%`E>1LwV4qzWr4!%bL8}MrES-zNt?g7`^>>)g`8j;ZlaMIMt_Q+%5uP3}_ zJbibKl6l1or6?g|z_=K&p0<5+PjhKXoyX*FJgJw02Ye}M8eKstuD?sR*Xu?R#96XF zfL*z29G42N6Jz7lg7r=cVm>A9p&|nA{lKJ${`4V$w3f1VKJ8567PRe`~Boaej;dYtX=qlqSzBX*i&&-KY+{n+NcNnvno8; z*YenBxWK1IJ>m~U0rf-S*vbyT%7FT5QOD?(zXjAk*?9}r&iId8X?)L&I&-1(^~T9L z&`&hLpEtM>T)j2-u{Q|o*+?kEWT_J0Ef>E~)9W|KJ{M_Q^Tq*NZHWl4_$C1W^cDI( za5i`sK!23>Kvlby1jJj|9Y<|$YRuCrqgu-}_8kYrwMsQ&^$@k~o`6iL7EZwE#>oC{ zKH&E`z~hVpfcdC`=!5_FDoI8sa7Y z9kDm9d6K*XHN4rt5B(hH`dzU}(We`dY}@cc#a)D!hT=YQ(oh1^d?KGz?K%E~h9NiqDyM}ye2XG;kZtFH;kR?JVwO#@kTc@; zf7k5qU7GWGyRhQjq){eL$(8n8O#9M3)cmf7Mj0j@!#4wfHdMB11SUAm0H|})h@G?j zL9wjVpIq%vyEO|r{4BTL-Mg(VbL3nqgJ6FDVYhwAY{orb3t3hYs-c{dOhWRQSuruldb#3tOt$rVE4Hq zbdYC01#|Ap2VOExe=PvX2K7j@KT|~e+twkWyoWxPtbdUzG#D+%d9?T&V?F{Yw z9~bMNw%c5{w{o4wQ%W!+HMOtEus?D=HCPl_pIoIPAXM;&2%8$JYsa7vbQ-BmSt!Y$@apjiS6q#G&?X;`6Y7aGyivfM* zlJTK_q9xiyWZ*BR%f zUTsCq003danpfnkRy{(?Uy#i_YqscsKqfqS-f?+b&DVrtS7OW;y+ihqg8=9i!yXS} z9MC@jE;9$Aiy-Etg98_7JdBHIc3K^3b{%Eqv<)nl_UvSjeCUEQW2S>dodJV?aqES~ z?7;m#{;PJPUsAnf4TX(grT|f+jaB>hr#F1@9vW^vw zX#mK-lb<}7J~ykthE(l9g=5%{2xm{wQO-a2IL-79& z`8rsyvg-Ap{44>vJE5vys`SPM>ILe>SM|$@jz%||JxB`Z|EpfDuQDgL%2tX3JBR3i zA9{6Bx_gW1@&X)|k?YP}3>b7;R$L2isnxP&>)Wu4VqhR=JDH+4X$b6TQwv0DSs^>D zXZQ8MH(oYdu3mazKBbkK#Dbz?;wNA@M;#|>K25L7VcIFSej9EuC zC#Enn72wf6xFH4>^ut3XH1ZBe9q$0t6j~%oSZI1HJW!uw$NK!d<0M!SH+?lFc0b2* z(*#SyuLaV`<{uEmTuqipi@p|0V}HV2Rru@N_c&|gccJ0y<+F#`C=@(kHzicV4~m{p zex(Ldlyny5@4&uX@|_n%z(DFD;})Zfo&W}t836<7b@f-qNEm3)=fga*q)V#nD!u4khUn%1GTG+0S;W6|4-1=PO`ArU?vqjSm zuRu8Wto`w*3AcQ5RkHD1wse5;ZoBrN31g5BpY__uB%+h-bClJTh;92Qe#8V22#z6A zpA`8O7+^fxz0O}6&`!?iObzx3_K2_Qm#X==-2In){1g*fphuubd{w_x8N?=U#!o29 zd`v;Fw$g(mbCX(zwWk|-e4ve6u4s7Pt5VS+*3HT?%~$73cd6(m`+7Z8X& zFooZ0U-C#~gnt8#pqt?x)e+(Tq%7OsJXjpoQ(#$VV_-ty-7 zdt~o#eQw}VW6q)3H*w)1P&4|ID{w=oh+@25`b(4Vuv_QlSa z<6uME$<) z`0>01*>9)Xsb88`=Z223;xfM};83=t3w20v`nZX};)dorp74q1>(Us4~W&6YS?%-snNuxj9_iq{E zjn(Ds*yqOltvX~7*}$OJW>TKQ;?J+EN|J!g)rle&5&K>wgbp&U8Rj> zz=b;O?4_G!IfU`ymZ>AxFJo6WP<;r~&xzr|QY{TVpOCI{+4me@D+0et2QA^Mu#y-6 ze{1FuctwayybrJW%_L+r?|^S9te)_@Jc^p@x}(E0U1zx&MLp6~7|yG}INgkru$)+c zN7*A+Qk|$TMv-DH9nVlqw<+ZbA436YIzcqi$N1qUX!-72kIK#1YTJSnRU1Jl0q0$v zr6NF-@u~zsxewzEO(0k4@QSbHhZ;hS2Qo&ug}6%q0yFOnE;oj9@a8M{+DDT`AKH%Z zX~r?x|F|vJtm$&GHh#bA9y5V}Dgw3Q5(%iPjusjp z1FRNdm*v>;;)(qz?k)ngb2p`<9tYn#5_;tr7dG_Qf`|bhlt|C(qWF0`=c7os4U8eC zaG!jhDuU1BJJ|w&+>2*Sk~4zRUOcZ@hz9q{TeDA-n}H@?3GPU+kMuqSa&eI05)Gpd zrsIk#T0p8(F|`8Y@t{M+P9orHB}h57Q8wZZjga|50jZ7EmsxV|94Tx10?Z>_5b$rv zdc-&>SlD~lpvFK7wXb^no)Y#gyvE6Dd4QRdSKod-BkZNx z=d9dv3~X^|9frQeH(nAcN5{c4Am1%fr($3%!65d}MAl$6Ehj`Ypc~lNI6o2%qOrNH z?6>94Nt6J%u5AWbUZr185K?Ll3=VFCc(NUeyAq=m0~TUnqr-^cEHB2nRjSoUvxd8% zjimur&pf36jSA}QTOeFH+-(txmuf@VAT!H5LwJY=wW=i|?})m_xo?z=-dyC7_CWAV z__!5((R8?tdF<2|&&DpY*T*#VN*PPS(Tmo)Ubrl!P&cc9q1hZH% z6saJiUX;dSgrHhju8Z+j)FMQ~3kn1!Fzla+Xp;}1a9#)5U55F1fI{&OE8Q$3v1Qc5 zG9d^+pAZw%RFqcJl;X(O5psXsl zrXao7+iN~IYNY2JOx~r~z%<3pVdG0u$S2H9@sNs*1dO|oDXC7k;?;^jWf)cH*wp5$ zMuSzf7vK^Pqhh%9w$2mb1KTo9G*Qu~Iz%p;4rPw zaq?02Q~q?+BjqDYPUxeom)(5qMq?@$$Ew(TvOskFD3>jzNCt_L05Wa8WiiHpH&u#h zqIxZG%@rcZm!+JsJ2p6~-^DX3HBFdNktvLCr@`GO0^gNAk*4!e4YvR4Ag_?>1v z9wy7(G3-1)h^+4X(722KsmfsiY_PFbP)`{l5?L8uyT%GgYQc7Ag}{WdVJGSU{}s>O zpVST5j{UAA%pA@kz-OOsfTbiY?dJ_8oux|$zPiECvKPeM-PcL^uhS4Sqrhi!5{1x*r1W%xQP#%>&qlqMZy=ch=c36h0F7o9UnxYI5= zt<(~+M`RJ_3_RHkh8F6x^=lgNW#@vzf=$GrQksyO@TO!f#cK|TqFzS0>}faybQ+v` zE0ArrBk;vD4ZlLw$(mA4VWL$De%E%}gl{yM71rL#!XnkyxrNQCPJ8U_@hrc(M6)L> z7YU;dmM0(BYj)Jv;%mBoA9H@FPiNAWW#0(ACiBybZfIY$n@%gOCasROGqhw2kqzg! zzDx#$R-QRvi3J}r&eeD@e<+m>ICZhWBw}N$htb5ho?=Og-HFM|smJu+(s*Hjws(#L)=b4e>1kakB|y?^9Tw^F z>tPgEpb3}Gv_O(k6IE?4cqn4g&sg@@jyNnh(w zFh0X6;Z)=I1LwxLi(xI*`?Q@mzkA%6M^|Vi%Mr5PFI^4DnS%0ql_hEAU~uG1 zHhDBEn%o%7Sq%Vd0Q8kHA@>DFQX5NO>Fq|8fIxSd@Y9s%9Ad~*nZXR$OF!Ubofz5n zoVYwPn%TqRaQ-{QYRAYo?lsPNYvE?W&93rlg1s}`Ou`^cG*l}?-uw#s&O&_V+q6nO zPFhi=L`L0XiLbO(g_fACR*~Ode4$vGgR@MSnHb^j|l*G{1mo6d0`;(%n{r# zICWg-lBQ7Z>I%SFkU)0@D=4G9ZaJ!R_<#cBAJU@+wD4go6vgVGWm8zti*^te*5$^8ZbRIOeO8v#(atiXaf^l5AH~d)fEss1Lmz5G-oG(u z3js%e31_t_2bH`G(Nk^3T7FW4tIOxHn>EL*v9TEyd}CX=*#u53zHJXtU9rR`dzAz9 zBaE#pMADd{I0z!C5V8z(fYR2dArF_#5P{+}O${H&B0igqzZl!aSumY3tGuJKn8P>Dd_PLQTh!z3u^~I=&aUVGVl8s(OZK;j`7`-JLa^ zMEY198hsLlfa6!l{MRP2*Vme78WRPS#LDakq{0FglH+Z!YA11@i)`$^fFKrhdn zj|by?&26F}vdAqW=!8jKi~Fk6C}kNkql?f-`x0)Igp`*$0bZ2wd8P}T6_5G6HWE}U zkw>+gg?w)5t0pAx44idX#^()Xt;39)Y!R0#PkZMw`T#(CjuntWot0*sU;mkp^{U&u zuBW9ZJN&z!cT?-t_gJrjLOSolQ{P2Un*7!69PY5!oSs*I9nB#V>(+e?-LmQ8e4|k>#_ErykXACQ-?#-qd%D zd5DMvws%jh^Ns^(CZpGiS=m8Bx9z}!e%t3R+1Cf+ksdhwN8`CWJ}ntfFR7Ich|D>J zU{?e2b@x0V1)d_#ytYuiClMp;&$ zPEdjN6T!Hj%1h>Qyq}j7SKCS$e)z~8Nh~`?>vtrmBFY#1&iefWM83m#hK^x62y~Uh z5j2+|2@*#V)pKk8x0?QsKvl%%6sIEo>B2mmp%?{D$K5;phg}H3KOA(c54Vk8O9`jf7 zUq}7Ya&g+sjdFbZf^Ei0Ljm~;b7TjtDVKxNm5nt2JcKq7ta#u<%w{TTu?pK-y2JST--hAJ?FsbrR;g=r*}0^l!xxqqDotcgtFd# z$^>rN`>zzg38fBr7S}xjvvu9SFI??q?8d5!M%?#Jqwag(sqXt8?fG+>J-F_Bn&i6g z&2_6y%)56a3F`=du^)WdU?la*Chlko&qfVFUaUYdJscDM8BjKvQV~y%5uFYattmk~ z(Xb6eO*4^*a&?bp3~S*VfpZoU#MoMA_UFE1ymPQw<` z05y6`t?N^F_V86jZ%T=lFe}GP=$}N1>p}iZG0wf3s)LEjY}qa&+l{Sr*wuRf~H0e>5kg$i=Aa`DIa}z)9?EdLojk)d| zNH|=o)}79pec9Q#xtrLJoc5U*qpJhO>~!NUO&!GQ*HUYh z4Ji5R(E3w6gT_oB-$e%CFe|_17u2gyF!^3!8-zVjHW1O%<8Hc^axS=`c)(Gxj8Q8;G0v3qbbY|5FHBe^ zH&4z@w%6r>>f9@YIfxyI+!228=^Nu$#p?S>{5%{ga$lv87lYbf?|w)gVWW6q=`<5F z>Y*%q!Hx{Zr%^zQBj&uUMYe}@!7JvixH^Gh=B`|4HSZB zmr*e?j2uw539eI0SprkKa5$o?V+&+pJ(boLz@*D@0Zhgl<0P?Mzr#MU4i85&7U6}X zJEhtTNoU>JYlS#2TsV?s9QmmkPVh^H8zH0q3Yjab(9;>_QQl|{>MVul;_)cfm<`|r za(E)%_p&EZjeaOzY|6KE`%oRW+c<3S8}BNSQNn-BYZ4p~=YlttKZ#j+dA{PX$=N%= z&Kp0NZDt=W-uL)7uHLEcry>PVDpr-*1KVl2>GTMYQ*p*H(6VC{2VAxte@9rx0OIV8 zmW7We@Id#9_>KtNXJor|a>}U|;#nm-yQle}6*6HrJu3_6=)@b1a~U{Q^T)S!C+t0~ zru)Id>B1FJWaiU}dAJM@)BccVs*qS3+7>YkhEy>-LLDS?6k|*x*5+~*%<&~lRbM}hA64M+*cht=4os{p*lGbZYNpD5yE})_mc?M|Nnw$J1WtR z_^*l91JQ>6f@rPA;8JeaDCJUA?LU(4ti@5%9U3Lwva|jd(k+ORuJeCIx~EcY%3l&~ zPLyc#U842&obrN*QF-IEwMRMpLSD=VRGV5B*bt#m!X6JNJqLwm|2GucDN3OkCsOFp zD22wHNTH2SrqBuk4MCu}5U8UB>Wflnc9cRdRtp# z{tc+fk4V4ecmQzlDnOJmc+$!{GdRbr286#EAFK&pVBGLMEc{LqBcyiSI>IkBhS-T0 zMc?T?aEpwKXoyZGrrTagls9{D`D~Ui)tO8__Kkn+lm^WpIWLW1ghLE#JjRg#=C3+v z^YOU6?0eIwUo@U?J$nV?Szy0!N;fL9ONL1N?(!AciUUet;-lC|9v$%)E7_H5Pg%_! zqpLYTx|*HnYW7sQn)Cm|YIgo^HUGyg2vGINIu8HsIzAp-v5r4GaUIX`x$8LNzgou& z|8gCF6AIkE+LM>N=Ooad(G$lN_RLB2ZW!iE(nVX+bA&myD!7s}r& zm@@lf?&Qo&8L%L(a<<$)3RXn#ufP~X`tIaLqSheP&!UAr2nKv#VjHJ0o&5l^j3|IX zdX?~ZxU`h!khd+4;L`SGlwJlTfHSAxguZd^i>kQ)Z}Q1%8BctAdfA0a?&mT@_)&_| zWqs7n5~gzqfPw_lrS=vU`2Qu(A1W*mE#(N8ieU{!Y?&qUK&5MkFtHxva40KRq?$j| zGY1;8D=+&|Ir7>a+lHfB)kXaz;-;N~sEktx_R+e=4TP|M82+q!W z(3rfuCq#t?K%y)!$2Sh}Q-TVd)5*T7iEp40hX(cxFA<02tlT$*zPRT!ez9km?h~_Q z+-ve@d{)jRixBG6qlBi`?W>!fIhs#&9{t(~`GiZNbIOMUgF=1ND(lH^Rn$w3UAP1PR|^UgOjn`!B`&R>RR5VxoVq8`)HIk zi{x(69a>}!ko~Dpgz@&kdoC;d*gacMn_Fvb*t{J1`AnL50Uu9dO3$J<(#2N@(JL%? zZ=8P2#9VtBhZOYVy0 z#h$F37yUXL%?jT#W(BW2pFZbj<$Nh>u2ym{E6g9QbzNl9(#{eNrS8ZuB>7N>9CkAd zYpMTY_A?G!nI4(ohvma(6&4CVB5$fyy66ps>354x&pVbd&zCb|i_C6q{6A=eJia3< z*EdHfF)w?LOep>dJ1%DyE}HF*PIdl{r#le5^sYb$YaDY3z(P8T(>BIGlQ-&EUr(dI z^)h2t3D_>Kl0K>gpo60TnnlYob z*?KOYEEgW>-IezMCQ^Ao_~k|20@Dktb}JaOV7OSd1JnnF*6}J=^A>zR<>t% zM!2);8Hsd@3#ByPJu7g-*X0aAe7@Q?E9-%kKt#!Jmy%fFWkYIB(=JHAi*6xgICexb zDs_wH5i>$&-qTt6mRlP#ALo(=5-@ww9o79iotLpjoO2-1b9FUm<>b2qC-X4Y&c?|U zJ;tsL=3KadVHQkL-m5}C8w(3Qf|l*t7#*RYtemPSNcU;hhAB}BQmM}!wwrZbh^`7} z(zOxGFRTsOn^xPkAzO7byEdHm%6s4|UIib^%NaK&a?X+tHOTr**M<&G0N&9s&XttH zR%XhHoN2Q-gLUXKF`JoVmWc^)-zv*K3ba#W+-2Yra^{JI%rdbos#L-F^o$lKL3OfB zKF}qSuS)O2~3qm@*JuCiWW3#JMcJ^Ry3jiHy=EA}j<^k7Z(h zq~JQs#4OLso#b*MneEYIBj;L>@bbUc`v4bJAJKSVDV*(E8+Y0m`A5R@#Lk>3p z{BZPr;z-^HUF^>bX?Ks|dQrN#Y2D{0+^i+(c9E;n!yiybDS1+}M=pnFE6x~zAHdOg zKU*(SPOI;y#p5(H7?VZ3m)X^l1mRt2!3X zxm{%_ta9&;&q&)V&UB;}x($|#KHEX@A*!P_B~o>aKomIdj;lV3~3HzPL-N z6&NzJjaIF5!NQ&Dd!(1u2A~NxV<`;oDD2@fViH3$>lkN3qvqg)t-tkoOWe*Km&!^% zc&MR#A-iaS+&gT~USr;A)806@Wyq#7__d-G6aF;i8QE_(%%pnrukFYP{)m{0<#NMM zTZan6e)@~+OsMuo>WPFO77iT1tm?gTC}%<_aj&v`T6W=#x1n)$g&Mn*O(M@P(t%o|MAP@a&QxE9May&T_R+G69%#8q*fdd? z%kIfD=8bpc>=h8DzPO;LF>NyCE4M$^FjbCSZjeqKZAo2$qBCUNkNbn9%rMO(myp`= znXGj0J%kVrB}u-~6>sZO`I)h8#2NUgGT6o)>>73DtlH}fT|A_kvj`z;_tRqIaWKxm zc<3HG*rO}PnPL70bmY5!o+u-F%@xMEVtPYkK7KPRXHBL9*>JV;tYX-kX{ z|CU}HK8kVn&Q9Dy7N^t2-s05y&DdL>#8~p8yjk#~&l}azDNe@e#y-&fm>MV@V%bnf`y>RAh zZ=jdVD7}Pxb~E1l>+IDz0t(D&Bh7mFw|?&Iv>74ywz#~%3{WeQ?XDYZ2hBPELPAOZ zElq8be5En_`^Xrc-d)J%KnW(PyXD(E-^wLof1wCa&tzadebX5sBX#8MC9YlcSUx)K z`$9mWu{jjo0g`D`Q<<>y?%1HcLREU0Qn*jC-PBRT6?WM>WuD@`jjjUQt4gcj?qcI~ zIKPv-z~#_GX44~sG7bN6x0^ddQ#CSOPmGf^psReXZj7!9nZS$XL)BJiQlyzYrseYS zy-Sz4gMW}x>46OC{$gLbG5hYg)0p3eNq1&9HlXKCEW0T=nBpHi=C;v2M`sQ1Kdj%d z#|A$p{|tUiPWunflFPS^8SD?H_~id%2amgLT+c~auk?SV-?YbO@$cXdhRqo^Z+OAz zMWd&U38eUa^8aaL7mX_zH*eCMS3Y=U*0fpj&$L-`I%m>6xx8rXG=D(e7DyR0O&%&3 zK2Kio!C-yaW7GP{tFtEcl+O$vEA8@K9UDmVPaCsjbpGhsxznFp@XVb4Gwyn6(BwzQ z-9GNtvC^(I`QMnqqi^+%zCHKR=LS7V~o8*QG3o9@*0mL)~On9x&aaR4F>mKCKRO5Nj zbCrn6iyDd&HSJ--?XIt7%$?U=BP;vUX?V+m`7V@sSuuyjohzS(?iFGyIZBK*ETCke ztUB+Hh?3)^HKA5HCbXvQ9?Xn)Bd->sq#PwY^+)2{&gn>=oUDejT;X)Sez3#e_o7@edcDq_$p z$L}|uHq-h?ytn-~JZ#MO>*^Y(V|RJOoHUTQmo@!Rj%$c;+)7Z+AJgSC%g=$!7diHW z+Gn%>Z4x?mwI;IXo4TJH^T0*BXcmS;jPZ`AU#3jI$V(>tvUK>d)A~x&UM1FM#ra;D zywIe4h8an74Fi5V1M+%-%YI_tl#d6VCq(gA0vs00AXWQ7hgn{XvFUVBHxsXd?Iv}Q zN88IpB<>KF-Vm(Y=C{azwm33S6v%U>MR(-_MCy!sS{io!RN>(>>xyb7vm)d9$*a8* zOsK^-$3R01CNqrX?hsY~eYhU{I<`LJee(3{66_Sqh!2a>WK2qgBz1t841172Q%z{x zAHWe;o~&U?7%9xvI_4@i*?2GiO&I@F?^+XDlDbgt|M-=r#{1G8)phgGB2=VF+&XKp zyLntbhn(k)$E9&MFkj*OMS8k1HkV0!S2p<$9gX?mg{~}@Ip>^a%#vGWgZuW}H;nlx zBt!qvcZY>1;vULYd!lY=IbWfN=0> z59RPP4%jv~$epczmEk$-pTbPx1uKojtMo)Y50pVpONp{N=xi~^v<|OSF7WBx^r-=;r%@e`| z8}t$-1_-og&X6_ub}t#2`iJ2CUl-Wc*)z{H&U!HIYx=UxU%7g=G5?B@WghdRw7c79 zK(42*lvWM9yuYp^QY{SeIe}wBAsYiJq)dAmjBX+e_LPTyUMjqNcMTb;H#*IinSN5v zroSN!)mKBhJ?W)Z^4+!$AJ4P|;&{HVIKu$7R{o4jSY%USWsE%TwOH9soE8I~C6Q;g zB_!weKd+Uc-zziG<8)%lt8JEkwd^9)-S{HXSYlvlXTN&0kmf63o>6_2d34&2fa&@+ z!Go#|z#{spd?K;7$8E~QfsE_5BaL(2JM#9ogmO%IP=0u6OMlK}2Yl_z(~TMO##MGb znj6l0Qiy4PGHK-d1_!l_rkoKW9lAi4#pB}}>d(f!be>RU-f9sI_H7roG9-Slt{>rf z9}<3AZ={Ux^mth-SFIzFpB0eMNrs~4_903I!rPNY;x!i|BxtFmLd<^C$X2@7u`5H^ zQI1{MP-288CrL+RW_aiT?$OufzCAUC5YO!(x?b@&@&!b%o4TgY}VJ?f@KMxswxBR*F;!M^qoA|j{SXappC3k1I3sjb>jPv{z zZ^&`u5?m+^{*ascNLO3e&$WXu-D0<`M_39WM>PivQJoWu`E7d+*UqO>r~KLU`^#m` zO>{`2MftlxI97d_@xJavFI_JK_Qycgfl8O~+0qxgxDArOT`s@8i!9?m>paReA+p)Y zABv;^j?Nd$N!0PahV8An6IaVP)AL?6p3hu6vRW-#piE-1wbfSCimJv~weVw*sfB?t zHZNx)5fC}9i>j6eEnpzI2ZyT~Iwuj1&^jykGuP&$M%@^GsS$AiJBgTRj%h{5yr*2_ zZ(Hu;7N;>*qKos!x~_(^dF&5fm4-cLkDBn~U3+(&YxTqkZk=~wo=I?B3d>{-@$z;U zbK`hNR~lcg&B2Uh)EKIjPp#Rq^Yhd7#jlMb93sax+$QJY@2<<=HPbVv;a1L3d(Z^F zO1QYS7v;R*!*^bQhb`AyQmyl%u{EI+KcX%KZm-ULIw+Xk^#?i2bi`HnQBXnE6i4JP}A`Hm{TC5vbLt`ws@?ES`ExTVZ2qkcBX0l;k-&_Q<;5n}sHP z)ES{vyjSJfICZ{8%y8<#(O3+3pnd9bAoE278?J$76S+TGV_Ni(o;gVkf8*^GY8@D% z*Mi7d-mu{lU8*qSG_)J%!Uquj1?kQD%HE)}Vc6S1PfE?9H&hPfWcqjah?8U%63 z+U^VV2OnV~^2SfIrW)sF&Q{XG$MLlM!AEsM_u3c{dFyudOXTG|;B#F#CFu%^!Cgn_ zWi^dK(!d@|=K13+MLAwa<&J`JFqYM?h=kFuaJ`P^5o@*u^H}M)8V7aOXcUoCm_K&B zy6qQ(QEHh?rr^0qXE;4X&N3ju_6;vhb`m%gY(_hU?F*Ps4~n8q31Ub{C}2 z_0>#(_c0K0>d{v}Ow^SXri&lsJ!#{z%!YKpaQ5^4#*B{gh6LA^I|)}3t>wjK%EZ)b zLK~CRrCgZ*DvqpNyFkrB>*UOKL%R6E#^fA#eP!+~)!bkmpOVjr@pe_ZZffNN>6tl} z8^||cjOpNfq~VBN!rxaAt`tLyGO}`CcHNFe5of$9a$S%1>(&ALyZS}Brh;#2)DR96 z6oSpLZX^??y~-bgnA}YKO0kwLCiSnzx#tiaARXR95( zh5>jSPV2bzIMv0qLQQgbi|?1U7&oM&027yFRlkpB0OkqVDn+-PIJU zEm4PCNFzXK({To%4iE7IAx%O<=U7kgBBnpbx*nY-8jW$5h6$@h|4d6qnL6u{FtYgmFbW8x9hYm(bxeEm?(+*VUJr!ae+H5aZyT2Q1Y{3Svk7;C%gMa0uLY8@!;-# zv`d-$9@-Vo%6V9ij}QPL@Aw|B65<_ec_xWFe}e;>gLyeq>}H{B7-I3+us_*6Gu)S| zi_&YovpII{n}AlI)bNj2Gbh2Pr%IPSKfUrbm29ogz1T_sZai6D?i9O=I1k}>)qdm= zw%RA`kre}UMlKr~CMhU9KA%vPaGIA_+f102dgnnu9uY?3jn|ip> zmzymaz%`2P2xYkIOW93{jflwy6(uhxn1XX#-Z~lP6;!^etWBdU^!-5{@JJwt<9!v8 zIL}^#w95M)_k+vc(({g&%;cA3I)8{HwJ zy^m|DZivYnZeUOjgQqj{1iP&~_i99(02xj$M1=|>TJ2A=We@RL$Odx@F-p2`OwW43 zU60C68`7fn8tZYKX0w|pOMBi8=nUr(>uAhHd3X5T-fENr`G0Xy*LZAJ&I>Op4ZUi4 zjl4MjbC)mFC0wK$BXKVXd>U!`L3$hIeXnNg_zbblqeZhIbXM;}%=LY8no(&ER%nRfPdIs(be3t6}V0hub-}aTy80Qa842bUqNW0qyF&GZFtydb&c+W7GU#tra4>I+T| zb`4%1d^0#H*eB2{Fg`FL&?oSZz@(Hw9e+`;&2oF4VD-5E@89srMbe6kf~U*B7X>d2 z)C<-Rq^Ab1@&6F0Kc&zYoEl%tcwa$W*`1!A%;XJsW#)KRfjgm=tBiN|POtUb6Es&! z$fvB3Y?X(eb0baU(68T(qS>Z*Mm_nW@E0mx@F>!^*}xcTTv#z@iR#;}lb1btvd`|D z_E1*?Cv_i{>3MU2@Lxt-x4RBm1N-{W3End!=oosg|vw;R}VP?0`hYcF(S8>&8%4g2!B`taJIULSQ@BHbf_S|fwaq9{T zQjN0}d%z7kp87yje$rR#>neY#;#!XVu-+_><+=y`ta~jdH6T}p5Dq1Z%T!u@)Hv-S z(eJXJa=F2RLTWnw1PuNzZC85c6DlG4o`{&y=MocMNa)>1g>Q>)$P1R_l<4FBWP9 z3+&YDakUEzCL@!o0$@2VUwsGG{F4#^Oj9``;cjnAgH%j$E}?kWMGVjPI(kk`_wYTB z%C#P;#;nT2f}&et3s+boyqWm7r|5r^yzD!J9pobq=`K!_?&+l&TpLW5vSe5N8Ajd#3iymMZ_VpIWuPz4%LXlOQsHWO zYKqMLxf&$j33|yVjQ<7#pxU^RV@s7d3i9&wC`{yBBC?hWn{}??b`6!fmLXl8m0R?v zjFdb;mItqQ?xzA=$-nC(sUBtjP&C6R3n&8v^O+FKgnmhg^m zBJI6~KC2f1+f zTi?CUMSYK6zds&)KIfick87{J_S$Q$z1FidAK*&attYMOKmLM~TZhX#(DmrNOad(Y zDpZ~da$MGq#D&o8@@VSF(RHyrIiFNvov7q5sp6kJ30K;~T;*2<=e6YPlBk$9^$p;- z2WdkJSgcR^08*Z-W3eJ#wZixNarb0&EC^jZs&)3{RX+^?rxNMw<8$0J@bn%x7$7^? zd{?Rg^uO|)0Az~>ZXd65Z3qTW?5_>WkUC&#JFe4dw>XG~(3whXC*y?C2%HV`Y)`9* z(NXXLZBaI~5I6i_P`NLI=(GP9pN{Rh+S zSh9-~jU(JiYF;c8&dWcWlp{l-ArR+UEsq=W>&#AE;Ng58LP zfKv^nP{o)wv?jvw!2W6|h7Lr00h+Lbn(Q%~di>*Z9NYrE`sF6I*t`1(Dp3VrZbOhN z^I1f~Yz<#^F3X{#pKRyoL4$S;9#0&%^d%KsUI;z5ZbHts!_Z@olSE|eJ7JA!ps~3H zr%#v+>JeJr?yX@e*L4=OY8|i8{5N_Z(q>xg zCaSg#lq&8SMAeO#E%0kF90T^FGv;&`$hB`~QXTC!QwyplLjp7&C#@o%xj=OLJUm!^ zyz>m&Ma=0BKM?-^!-%$zu1BaF-@R7_ib+qF!pJDg4mwNIxmQ#vNZeO6xMx13{vW1y zL!SgUGT?%%W5nQENb2*1zeXpJn`VYZL)zb5fX-EiF+FZWRoqakLms%jEu`tA)?o3y z`}0BE(qPuTOWRS~Z3#dd$@74;-~NtjHtZU%(M$IRXHe}?8Yt_|!b9{uh)iFGuw*OY z$n{(B-uZMB4Ff!n7$PWL_0uqZ{QcaQoJQcxX`GeULk)1p1~uOgchr>nJpufY64p-9 z5scX-0nO-rHbWv| zKyjy`KP~`Q(F&)|=`&3sj}qdtU;n4j>0igCnwz-7cZ|0ko0aGTI93XB2z529z-W~N zbAEmqTkXz%=rDc$1592WG&{FDK^WH?MBWcXKBElq4MLmW@TTRqz=eQZcx@H6**k}G zyiqWkDHBPh)_Y;QQihdbRA~s+Hl;6b9ZUik7LiY#qj{fU!qk~>cEL`!da)dkqCs-5 z#185-WUC_~EdM~K*_qHIX7U-#oQ|X?R=!)K|4N&zxoO-78F!7*49Ql5=BA%&P(C|1{ z!s_L_Xyl|B-*Gy~$aQLPTLd(0b3GPk)oJE*#Cj+{NBO#bPzrJ~2P4;UI&D+yX+fkG zl%fSV#GLb%JFO~5Hd4D@%55Zm<1XB^%WeI|phzyV1k8uU&>ttUjNaP^Yuiaq+5k0X zz;yXB_<^}{ri250KQ1S62>_^_-wnjK{lZX)Ri_;hu^re?R&ZKuHr<}z6V0uS6wQR| z$~%!b(4o?V`!(?knh?JnrMo||KHQ^s7oUKO&@G!_ra~)y+1_P*CNfr>1q>@K{tyOE z$^@b&Q>dZeIuS?I2lE@|XOib(LX_)U2}E5^0qx-RsM|6K@1DYpgB7U_T!lL6Ibn~) z)cpY>9lMH105_kfF5U>Beq1{T7vI%zD0yWVtYkw7g@1#NweHekpdd6I$yDHP*~Gv! zJc&V8{WZg?izx>Qx9$6PD$GggIGX8RtHFGnaLx=H8^X$G+Z}+x3=!~ND+%kq9o!_W z#@NNXzCc`XC6|fNu(MV06>#P!n`Vr}Ep{%0C9cV|YtXiTV|nB*Tf;KR^&$pzq8$cU z1fqLptVqkL!*?HtJE<3_(t-b|1K4P`^tl#C>+2RmfAe`*ShT);7#BiwkE^iU+vTSr zqw7UznKp&CWjgn$2*#%ODxghxR~O>WZ&n@QLBxR-;WVReQ!i_d!BZ_li&m>9P@^E2 zrNN&9j1|tqnOsu|!ZkcG)`KJ2InY2js&9XrAmzG~=C4N#_JW`AF7dX|T|2}R^1R2b z%PgoLe0+~kIMIOF5{xaY}I9N+P>EY(G#FxC;0uP-wl*3#O5-oDATncG9E4^s$6W3f5CN;medtoTW^vZ>m|ydq zKTwr0yzQ5SI5qvLM7MA_mgkgGH<*Zt?$yyRF=^`_al-6#gP=xYr|jnWoS2vxM!Fja zk{_1sg@GZ;JxOkvC3)V2* zI*I89&D{$kA>|X8dcN-@uv2<$U76-0rNbMCV1?-dlQL@59eCkp%k3&DEq&h14bn@*(QSRVp>KUli|@?{k627et@L6-E&Bj^*Y76$X0C?M&abB~eqf!5jg`wHEOHybK92x~Z$2p23dy() z981={;|8&gQVZ$3;XTI^iCMuKk6Of36*i!Jp!mDC@!&<m~iFb^ehm@kmOOVso+*D4O?a9#ffA*+R+RXZf_ z#Z5Z!t_{Q(?eNYA2YI=8NE!}(B$vw#N5Wn>WZ4&*6fU9Usxfg_QI?*Op=p9$eYs0L zAMWUc7YLaeq)b9uDfQ&-^9|T0PI?O)i|+hLG4gvr>MXT^yv8}$vX+Jgi83H0;wqvH z$c9WPCOC+@!E8q#KsFyqXO#P0&M6P41dKEv$Hu6Oib6v522fd28rc{YuYYh z0l-DrxQM%8Yc#*c5p_UnuIRZ3@LUo)srD5o4e&CF!hjLix#&u0^$v0&a)Ktp?~JT4 zIFNifxGKty3)!pW2lWb9=lgN_V^@Cgy)|m#K0KgaXEj`}i+5V1cq4;)!` zswf-6u~trk*U4arB_BC~&V#P!S#a7%(kW4zij6A|-TCoYF34x%IBUSecfZ0sW-6@Q z3xSZ&`v&$*cbwqmkVN*gTrzqBCM5P~V2}8#6&4wR!k-2lyYK)^m`omA!;dD?qnYU< zDo$z|1F0=kyR99@Q4qc-|A@hwLyI5Ef^b;!Mz#C6Mo1_pHG#aN7yO{!1wQM!+#7ZF z<<2gU)S$T?=*Jt6q&RaeF8tB)j`+ed9IoYE5ne$sam8b;!{9_rX5JCWA7`_r*I@MZ zLa;wBy=%;@X%o~SGx9EYDMrmD2=BayphH%Iz_h%4qX3S87B9G#d%t_A?lS()#Oxb` zgB>cC+F(m_J0cxmW)>)hBq>N(M`+bT)Y5D{$FxDA-NZs(8rt=@l@mFzr~hr`pp}HE z&D|B)>gcn=J1wi3|9+*m%m+1ETda|2vj538T8;l3#iS^$k5Srqal~vMNHMOC!8P#$ zjm_PA2!&0|DZPwb_d%eMACB`N_&T&4}d|$rsUTHR8N;J z#J4uS4dawRh=OQuC=JA^GS~IQ5%@&(5s4)qxcYL^(YuKC4YZbHFBdi>o=WaVr7Xc} zbOc`AJ>H?V)MryEkDVuW)CG>?TEiEin+r^lTw%CuB!)?Ho=atQbzcR)wV0c5XMX=1 zVzoTp)uB|{;Ix9>?yc$YbnQ%c>2q!}X_zcs5*Dy9~zi)ya*zJ?E8r z?I<(2BzRnoyiY{=Yhu64e=COzbArf`-{G1y6P9m~?>FXxiyHtl{K=45LC$uoKW!D- z5|6@EM3-%IhThfT=ux|kF$maY5xrtf1EC58i|;xV#+1V;o6}nAS%PaLnnaL`e)kO3 zHk~-~@S!QxvX#Up_r#{>I&DFv9wAQJz%+r;H?_w0z&^WNlM3I=fZTs^fCCPvsm~kb z_n0)qn?^9#rGOZy4{4;+zkQVkuK=2aYcS0Yu6d9d5+mC=4YY?^l-4jk40?f}IMME-+-o2e!LY3o;f`@^2NcFQQiTMJgXTAg_ z<7o@W6CB0bG@PjVW`e`zK8FBZ#OEo;5upXtz};=TGpTP-BA^@Qb!5x%qxXPU(| ze%3^hk*)pyHj)S6G98zX@WH|}HYZ=2G9HB-N5;-5_;sY5@$g;f>2Amv#T$jCCuQj_ zYGG)>C6SZ2hNpk%&ZpyH&XyNZi%n zE}1^eYh>)q_2+nEsAFw73Ya~A8UR=<@<^}ULB=gV;0}tJml4yUaRJr2(q%2b61F|{ zu?M|fqf(;D({Sn6eAkyg93O@xG_v8}%|+)ZkAl}d(ZUXt>Pdgp;V*LX7D`X;EJIuT zM7zMF`KK0Or+mUcfOk$4^D=vf+{97=Ut>Y3>Q1e?zaf{pfI5gmJ!)Ibr z9C0j)X7<+DY>wB!8R=kNY#_I=5+*BP9%B=!maSYn%Xv!QfUSDiMndk}Z2!Z!eI&OX z<;J1GqUrHGp~6-FvaO3zSSGMY-(>*P1p;btZzfr(>r_Voar?Tl!Kb`-qU|qY?})@r zu(H<+4=EPSZd}Jrlb_f0%h1DfM;hnwqPuV4eA*|Y^>o>}lmAf9mtv_6!8R+snQLR& zzJmr2#*eS2%pXk5sjqJm&C9%e*+x47&%C94kSmbGhZ;pa!ypHz-O+4mtZ+-s$6DvZ ztuNX%!`uOQaRfk&yf#2lrtwBaxb=(46b{>%6bLX^bEk5D)AyO&>YE#IxK`jSKQPO{ z%|rokpcWvM7iEm67D~FFOq%WMYlDA@sJwi179MWaV$;CwDI*C>ZQne=B@%rFyH$*=e+^3En!v4NV09tQVRcFs zV?|&rxe;b=a4^(5CvpE7T6Pt8XE-u(*!AZFKo!j2$?N2ZHGe+8HwgyR{w;ThVMqIO zfbrX57`g*2QI5c7LaD*kL|7^+%pzj7?GsxV{eNQnM5SN#6Z#KR`7b@ud$WC#i%qdj zRi~*6{ZFNT{%e~`Z|S{lnoTj!tsm^A<`~xH<*i#6kr!sRWh=Iq6@6GlL|9l}#5%Lb z{qlNGuVcndi`FbP4O3oE8m7p_s++N>u&~IiRvL>I7S1)x6j(~_8Mzmbb*qp zq$H;*vf5uMNY-yl-M(Jgsbt6J#$?O3C)Ahpap?)^O1hGfnvtGaaDJp)^O3QM(Nm3GHrj8%Xn#CXaUmzt3N#*Z^k z%~PEcvSQMs71`D|S-&80$x3CmTz|us<5i#(r03^-{`EICn@-5->-FiGndxiNWObk+ znQBdrlM^EKA%O!2&V0!lu_{~%S5~fGnX^(>d)r>rrz9j(yJc%cT(~J-vH4O{s3WUV zVzcETs-%y~3(F5xqLt``n3&kugqZkfIn-C5U`>9XxPv66kD|AARBVr{V{JOMNDe92 zC#_D(RMO?F#N>4*wR2L|68+8ryT*L}!kVxG%X;M_rEpVfk*q#%R6Y&KUzVM}X5H!? ziu_fIerJw#vDx;%aZ%yQEhdlq!)|z~Jq&AebJv>H9>%qiIpHQ-4?}oVRJhsJ!x)|u zxz?oiQ2o7ZJ#0NFfm&RD$;*~?;sJ_{p(o?RPXe`@_6nqJ@DxaUf3HAVyJyoKulsw| ztOZ8QCCo9?j{o^YnLUb2O17&r(n?CcFHuL8)RgE_N>uhS-SQ{r`^gRcz5Epag)`oo z_{yTGt9)W2-kTR2E&JFy=;K$#TH|8pD4~h-BjoT{{kSb-H~njKTyi{(K)4cRNeYn_ z+q{%0eM;1tRhbEL?JWK3sO0F}rNz&mp8vgaL#h2HPt~tZOHIzokku*bllrwe1(rkW zuk84_CR3H=Vq<*yvWno{^1IvgbBhAEO;Y5>(T!PN1%}P3%BK9aa*f)aaAU_8>Difg zzc_XA)2+GN)3+&KDMvzz7RZGQ^@a03crA6ByucXS?}ea|i>HRam%T7VrU~|>{{6_v zxP(wS(V~wW;y=Y-c}1DHdC}qbx2JDhnj`nwqo@<~`&S%Tv46#y_?*}rB~4kKygFr1 z!J4#>6*=1WiC5L9$BrF;^Q$1U#So_?CnY8vTKUD|v@yyO>yps0(ZqO9#o@NzzHLNpqz`x=y-q-4D9U zPC-stE^`bs4Mz;sMqlG9<3)LhoFx~yI=fDDEp}VvcIe*@8)EY`v=sWI#n2tC7@Jos+V9eS z*^baMSSas|A}ahge*QyRK_-VJ6ZbmJ=(M(7?gImhOt*8 zuQI8k=?=WO6K_|vFei~oe%@J3%FfB8J@WupEnc0jJ)vbA#PxDJ5Adjjpn6jo54ZrZP{-kzSa_JYsOxw>ZAD?zdiw`SFI6S;Tobr+7Y9z z8X=o*Zlt~aMMRiUr#wrKDbI4tTgJYXsnpwu6MiWjUY!8pt8ZLj(vw{l5W8l7j7j4V zL7^CyRO3FQuf>B$)1DQ^A?IPL<%M_|w-8a#d+7nH9K9HbOIIibdEgEi!Is-1Qj*LA zBp?n04~f=hJN(Z`?!9m-zg0hQ?ax6zfVkHDxSW^n1o6F35p;IU_GTQnYs40cs-vtbKJmuSIl%f7l`tSoHuU-ZqenE`n>mi7J#=~7+ zgLkrD5v;8f4`_O624sMUJ^u}0)c1w6jD3tqrY{eq!io^9Z33Ke{E!1HLmF&W3zFsi zIV@5ye?$qp1Js^~Q_+*hZR}hXBqHKT?FSM@bb}O6k4W;Z5QtPCA&Ec&k_7#(aU8ap zHvtQT`>GB38gc2qW$Xh4Sj-r8E{sWC5PzGE0~`>dl>rZV3~KwI*n8OgVJO-Fk%%3! zf^AjUWn-MaevvwJ?z2&h9eLp}(Et^}C9ceXR#Trom$3u{?4B1&iC#Orno0X$pgsyA z-S$CDISMkB!i71+^utwAs5}s1s$qA7fHg8U9?xtf!ZQ5xBHC5}Jj0&xlO2SI|7oPo z&MB#r+)QWe%>>ZNq)yaGohFsmVnuOE+9bEMGJ)J#m)V&vy4w-flyeED%(D@~ETU=z z5k9FuqFMCBxc`h0glBMfrungjcRVp=3)C+B(ecjAOAPuqON5s)N8(3HENy-lgc4hz z?PH8Bc6N;6(RD+RZ1ahh*Nqm7ZgF{U8q{LJEtQG}_XI+B1-|nOJz`rV-m@CVM})t| zMof$LUE}zOM{KLady$l9qU@$twn|$i73OHWwxXhC-1k!LWM`QJ2XkC>0wEtC29CCH zYm1%2S3CKH@lk|p5k$PC3GDw3`l_KflfJ%BRQh;hU+obWHq`Nf$<3Fc;zKYV^TkCZ z8G?wWl+DZuX*K%i$s<@m93BB{ClRLt!HdLYmA*OXA`d2`g|(1r6{ZQU>pJ!fCHM?w zAFP?k*aj#u&btvo%x=1ag|u_sXIeBj9%}6m_ZM;br}n75`=|Ed3!L^DWjR9GZmu&^ zb@}v(U5KSo-SlBGW92Y+F3F1`Y9Ji~8Ck!y7?)jm94FqfnT`+4ulX|5@xk$KmzHFv zmd)JizX9ljShxTH000000RR910Mm#Bk^lez0Mr9ma{vGU0MrCRT6mmeU}Rum-~nPW zAZB7<1j2nl%mU^y000Zf0PO$(0C=2ZU}pZyIDvtcfrDu#(-{T^hJFam_?p3(k&}Ue ziGh`w0R|Wa7!V*J%{7I=fkEM!0`r0Y2N+_Q4>34_)W8)e03jm)x91Hr0C=2ZU|?Wo zfM6L0Mg|aw0rD8Zd=>@=AkD+T!N3G$GcYJTSoloxnZp17V08))7@jdec>fi4G^dJGq6CJbR+>0E0iCI%hf`obme@B>L0)DYRpF9?zbGsG5d~TF$NP z|J#0hmbIiC*^Sg%`5Px# literal 0 HcmV?d00001 diff --git a/static/fonts/Simple-Line-Icons.woff2 b/static/fonts/Simple-Line-Icons.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..c49fccf510eb41b4aac2bfa21b3b87c254083bf8 GIT binary patch literal 30064 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

`i;W%DyRrxz&7=UY}f`DBwGN z^QCTGvyO~ey9-qw{@>K>*0ETVw6;Tilw(vetE!&HNSMvhCH4F|*L8W}J3TzIc5B!6 zX)TxLl{nFlFOSxB;2j)0hQZWfaCN|UD9kj_4@^De_#GQExLdpTyP#guE1OrO0fIS| zxSjBT(4D`0xgb6tBvdU<@K5*iOZQ*gSnDtyG3`*QWVP4SM&u7H&+jH&BW}m8EyH$^ zUXgJZm*E1jHtHi|Q5+XpdAO{^QB6mi?5b-JhnrSmxJ4K7BtY^5#({pvti`&@%0$Q2 zKS#P!{jl~ina%4b51z(3Ogng;jb=%q_{C)j33V+fF3{na1B>a)ssN0$vnHYY`KUED zSe%EkO*W05q|)u^)KjR<0`U9WGWZDyF78Jp?^|C;Z}dM?>i0LH*9cr>jv)Z zSG1v}2sOq?`&!|ERTez5|9SB5@8{1*IPxf3lJPZBH8|{t4*+#hIC2(Sb7kNbm z6L5j(={Se#lB)h)Z?$zT0;E07Zx+*lLpRocoYh0AW*YZ5e6RhG{FC6)EM*2&gS+2P=%z&>^(%zh`5z zgLD>8h=AJsbh+L}Rk}(EwwcJU$b;mCJY+Qk$h!y0R{kui%>TS2Y!d*nLks~4SNQ9) z_@oNH+XOzHqpn7mu?M>aX&%m)WOJ+5=CV`a#nsBSbb@Y4ZbER5|JF=n1T$aEvFF`% z8-85%3PC9P)l8!7DVWL9QIgqfw=i(zIglaV5Vzget2Hg3NE%x zMG{$n|nzfja!?p&3@OfNyE-nV9cbL$uVF_@@Ds2+#l8y|A%B{HUFYqllW(p zR@x8$6r9+@rTr0m3^WYA`nTXc-kTv?Ib*YdF^i)3iLZ!|?oauLC$Dw!FFrCq4)8^! z9f<$tq_#6A88-l*U5|6*8w z|2^F2HHfH+?GtdU=N<(1>)-kQxf;lef=?{q8^q}c;{U10NISx~>9|oRe%wJ6~J1Z65;d z=(Y!*>j%xC0;v553fqq6@+UKCkStbAzXT54{~JJX=;}#UR82R`|FxQ>ko#b@nkxiFP5uzZGB^NYkOyRZ~x%%==cN-fx_SjBnpke;_w6_ ziA2iC#K0ki|gkS{4 zU~zZ?kwm6YX>!>rAn>Q>huPq$!xLO><*_Z{#x>QeSTHQ zl@QmFh+!S&U=+V!GMCTwn2mY6+$U=%%-sDF*c0X0e{(tzN`gYJNgs*bmrdIOXr4D} zL+k<;Ra#hX@kL!cmQr3VX=8Y1Dc@Thj|+n^Y>o&HmsA0=J3^=QLAY~~=&_f~k5Jn; z)R&f{^GO-Qrm_z@tg1?WN4MB2?2;`*r_J6)?_SAa9Bi^N)jHilp^?UtyL*(Xw_6gN zVq2BM3^CmV1HNeMalw9r%K?`w`9_Qsb;9USryfMcm>WM(=LR ztbq{KsYn=gAjPWkGZ&b#+wVo}E6h;)z$*mnxA(jC`u}J29x*FqJ18o$4nFC} zvFgb!_ikRB;I!nhReeHgKO~2Rw(*{2fs?7--oNWX2~#Hr$OAGp&S@3{;Q|p$vsyJV z63qCm2it-^B&N~VshPmr?_?bhuVLSeQpw?@b@a+~P_2Usvtwz$OWH0x3p-3czA_cDba!pl*@^f+grlr^GBA&$-&697h8;)xLr3_o3DzV5aT_ z)%|p2>$8rdh~Cov;d(KTCq9$cBdU$K&(YSEG3Fq#7~Y#U=(*ZPH#jZytJaZyWV5Nb zuBy?y!`!)UdeWz5z6T3xW^!Sl+)C4KqsMGKhgKis1v8jlct;GKn~r_NUn5qCiaS|t zlpiO^fiDsu_NYOKPbvJX(^=L}s<sX%kAfVQ|%HgqUl`-14}f>Sf7oy0?Y+D3*=!i31@-J)gq_dxNCf6tVT XVaD<2gWrVGOVQy$mL_vR2><{9vXVmp literal 0 HcmV?d00001 diff --git a/static/img/icon128.png b/static/img/icon128.png index 8453f86a0e370bc41e9da5ea4f87a1a516a9b8e7..093959daa9e1af79c267aca8984b085d2d6e775d 100644 GIT binary patch delta 3682 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 Dg3qc` delta 4456 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 mp^z1uc2!<1#{ifdBt2;OwybsY7Kj=I0_kZRYt + + + + + + + +

+ + \ No newline at end of file diff --git a/static/update-0.4.0-fr.html b/static/update-0.4.0-fr.html new file mode 100644 index 0000000..2045b3a --- /dev/null +++ b/static/update-0.4.0-fr.html @@ -0,0 +1,29 @@ + + + + + + + + +
+

Mise à jour de Shazify

+

Shazify a été mis à jour avec de nouvelles fonctionnalités!

+
    +
  • Mise à jour de l'API Shazam.
    Gestion de la nouvelle méthode de connexion et de l'API de Shazam.
  • +
  • Libre de choisir.
    Sélectionnez manuellement le morceau que vous voulez sur Spotify.
    Définissez un morceau comme "non trouvé" si aucun des résultats ne vous convient.
  • +
  • Filtres.
    Filtrez les morceaux en fonction de leur statut (trouvé, non trouvé).
  • +
  • Aperçu du morceau!
    Écoutez l'aperçu d'un morceau directement dans Shazify.
  • +
  • Meilleure identification.
    Nous ne lions plus automatiquement un morceau sur Spotify si le titre ne correspond pas au tag Shazam.
  • +
  • Plus rapide que jamais.
    Morceaux triés par pages pour un affichage rapide.
    Stockage dans IndexedDB.
  • +
  • Interface améliorée.
    Interface propre. Nouvelles icônes.
  • +
  • Corrections de bugs.
    Shazify est plus fiable que jamais!
  • +
+

N'hésitez pas à me contacter ou réagir sur Twitter si vous avez des suggestions ou problèmes.

+

Merci d'utiliser Shazify !

+

Leeroy

+

+

+
+ + \ No newline at end of file