Commit 128343d3 authored by Laurent's avatar Laurent

Merge branch 'background-fetch' into 'master'

Background fetch

See merge request !4
parents a88a4ae0 2cc55962
......@@ -25,4 +25,18 @@ configurations.all {
}
}
}
}
\ No newline at end of file
}
def settingsGradlePath
if(project.hasProperty("appResourcesPath")){
settingsGradlePath = "$project.appResourcesPath/Android/settings.gradle";
} else {
settingsGradlePath = "$rootDir/../../app/App_Resources/Android/settings.gradle";
}
def settingsGradleFile = new File(settingsGradlePath);
if(settingsGradleFile.exists())
{
apply from: settingsGradleFile;
}
import groovy.json.JsonSlurper
task replaceSettings {
description "Replaces configuration settings."
def jsonSlurper = new JsonSlurper()
def pathToSettingsJson
if (project.hasProperty("appResourcesPath")) {
pathToSettingsJson = "$project.appResourcesPath/Android/settings.json";
} else {
pathToSettingsJson = "$rootDir/../../app/App_Resources/Android/settings.json";
}
def settingsJsonFile = file(pathToSettingsJson);
def settingsResolvedPath = settingsJsonFile.getAbsolutePath();
if(settingsJsonFile.exists())
{
println "\t Applying settings from $settingsResolvedPath"
String settingsGradleTemplate = """android {
defaultConfig {
applicationId = "__appId__"
if (__minSdkVersion__) {
minSdkVersion = __minSdkVersion__
}
if (__targetSdkVersion__) {
targetSdkVersion = __targetSdkVersion__
}
}
}"""
def settingsJsonContent = settingsJsonFile.getText("UTF-8");
def settingsMap = jsonSlurper.parseText(settingsJsonContent);
for ( setting in settingsMap ) {
def placeholder = "__${setting.key}__";
def settingValue = setting.value;
if (settingValue == null) {
settingValue = false
}
settingsGradleTemplate = settingsGradleTemplate.replaceAll( placeholder, settingValue as String);
}
new File( "$rootDir/temp_setting.gradle" ).write( settingsGradleTemplate, 'UTF-8');
apply from: "$rootDir/temp_setting.gradle";
}
}
{"appId":"fr.afi_sa.MyBibApp","minSdkVersion":null,"targetSdkVersion":null}
......@@ -19,9 +19,11 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<string>1.0.1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>Need to access camera to scan barcode of books, CD, DVD, ... that you want to search for, or scan your library card to autofill your identifier when you create an account</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiresFullScreen</key>
......@@ -49,4 +51,4 @@
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
\ No newline at end of file
</plist>
// You can add custom settings here
// for example you can uncomment the following line to force distribution code signing
// CODE_SIGN_IDENTITY = iPhone Distribution
CODE_SIGN_IDENTITY = Arnaud Lelache
// To build for device with XCode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html
// DEVELOPMENT_TEAM = YOUR_TEAM_ID;
DEVELOPMENT_TEAM = 7C2P69LB62;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
......@@ -6,7 +6,7 @@ import { Database, Manager, NSHTTPClient, BackgroundJobs } from './models';
import * as config from "./config/config";
import { AccountsController } from './controllers/accounts';
import * as Frames from 'ui/frame';
import * as Frames from 'tns-core-modules/ui/frame';
function myReloadPage() {
let frame: any = Frames.topmost();
if (frame && frame._currentEntry) {
......@@ -32,11 +32,14 @@ global.__onLiveSyncCore = myReloadPage;
Database.open('prod');
new BackgroundJobs().setupScheduleJob(Database.current())
new BackgroundJobs().setupScheduleJob(app)
let manager = new Manager(config.MANAGER_URL, new NSHTTPClient())
app.start({
moduleName: 'views/accounts/list-page',
bindingContext: new AccountsController(manager)
})
import { UpdateAccountsJob } from './models/background-jobs/update-accounts-job'
new UpdateAccountsJob().setup()
import { ObservableArray } from 'data/observable-array'
import { Observable } from 'data/observable'
import { Page } from 'ui/page'
import { ObservableArray } from 'tns-core-modules/data/observable-array'
import { Observable } from 'tns-core-modules/data/observable'
import { Page } from 'tns-core-modules/ui/page'
import { Controller } from './controller'
import { LibraryController } from './library'
import { MyBibAppController } from './mybibapp'
......
import { Observable } from 'data/observable';
import * as Frames from 'ui/frame';
import * as dialogs from 'ui/dialogs';
import { Label } from 'ui/label';
import { Observable } from 'tns-core-modules/data/observable';
import * as Frames from 'tns-core-modules/ui/frame';
import * as dialogs from 'tns-core-modules/ui/dialogs';
import { Label } from 'tns-core-modules/ui/label';
import * as Utils from "tns-core-modules/utils/utils"
let validator = require('validator');
......
import { ObservableArray } from 'data/observable-array';
import { ObservableArray } from 'tns-core-modules/data/observable-array';
import { Controller } from './controller';
import { BarcodeScanner } from 'nativescript-barcodescanner';
import { AccountsController } from '../controllers/accounts';
......@@ -20,10 +20,9 @@ import {
} from '../models/';
import { RadSideDrawer } from 'nativescript-ui-sidedrawer';
import { WebView } from 'ui/web-view';
import { WebView } from 'tns-core-modules/ui/web-view';
import { OAuthWebViewClient } from './utils/oauth-webview-client';
import { MbaWebView } from './utils/mba-web-view';
import * as LocalNotifications from "nativescript-local-notifications";
import * as imageSource from "tns-core-modules/image-source";
export class LibraryController extends Controller {
......@@ -68,16 +67,6 @@ export class LibraryController extends Controller {
}
public sendNotification(message: string) {
LocalNotifications.requestPermission();
LocalNotifications.schedule([{
body: message,
smallIcon: 'res://presence_online'
}]);
}
public goHomeAction() {
this.navigate({
moduleName: "views/accounts/list-page",
......
import { WebView } from 'ui/web-view'
import { WebView } from 'tns-core-modules/ui/web-view'
export class MbaWebView extends WebView {
......
import { WebView } from 'ui/web-view'
import { WebView } from 'tns-core-modules/ui/web-view'
import { Cookies } from '../../models/';
export class MbaWebView extends WebView {
......
import { Account, Cookies } from '../../models';
import { WebView } from 'ui/web-view';
import { WebView } from 'tns-core-modules/ui/web-view';
export class OAuthWebViewClient {
protected static _onTokenSet: any;
......
import { Account } from '../../models';
import { WebView } from 'ui/web-view';
import { WebView } from 'tns-core-modules/ui/web-view';
export declare class OAuthWebViewClient {
public static newWithWebView(webview: WebView): OAuthWebViewClient;
......
import { Account, Cookies } from '../../models';
import { WebView } from 'ui/web-view';
import { WebView } from 'tns-core-modules/ui/web-view';
var NSWKNavigationDelegate = (NSObject as any).extend(
......
import {ObservableArray} from 'data/observable-array';
import {Account} from './account';
import { ObservableArray } from 'tns-core-modules/data/observable-array';
import { Account } from './account';
export class Accounts extends ObservableArray<Account> {
}
declare var fr: any;
import { Database, Portal, Account } from '../models/'
import * as utils from 'utils/utils'
import * as ApplicationSettings from "tns-core-modules/application-settings"
android.app.job.JobService.extend("fr.afi_sa.MyBibApp.RefreshAccountsService", {
onStartJob: function(params:android.app.job.JobParameters):boolean {
if (!ApplicationSettings.getBoolean('background_refresh_on', false)) {
android.util.Log.d('MyBibApp', 'background refresh disabled')
this.jobFinished(params, true)
return true
}
let portal = new Portal()
let accounts = Database.current().findAll(Account)
accounts.reduce(
(promise: Promise<any>, account: Account) => {
return promise.then( ()=> {
android.util.Log.d('MyBibApp', 'refresh account: ' + account.label)
return portal.refresh(account).catch( (error) => {
android.util.Log.e('MyBibApp', error.message)
})
})
},
Promise.resolve())
.then( () => {
android.util.Log.d('MyBibApp', 'refresh accounts finished')
this.jobFinished(params, true);
})
return true
},
onStopJob: function(params:android.app.job.JobParameters):boolean {
this.jobFinished(params, false)
return true;
}
})
export class BackgroundJobs {
public setupScheduleJob(database: Database) {
let context = utils.ad.getApplicationContext()
let component = new android.content.ComponentName(context, fr.afi_sa.MyBibApp.RefreshAccountsService.class)
const builder = new (<any>android.app).job.JobInfo.Builder(1, component)
builder.setPeriodic(20 * 60 * 1000)
builder.setPersisted(true)
const jobScheduler = context.getSystemService((<any>android.content.Context).JOB_SCHEDULER_SERVICE)
console.log("Job Scheduled: " + jobScheduler.schedule(builder.build()))
return this
}
}
import { Database } from '../models/'
export declare class BackgroundJobs {
public setupScheduleJob(database: Database): this
}
import { Database } from '../models/'
export class BackgroundJobs {
public setupScheduleJob(database: Database) {
}
}
    
import * as Application from 'application'
import { WebView } from 'ui/web-view'
import * as Application from 'tns-core-modules/application'
import { WebView } from 'tns-core-modules/ui/web-view'
declare var android: any
declare var java: any
......
import { WebView } from 'ui/web-view'
import { WebView } from 'tns-core-modules/ui/web-view'
export class Cookies {
public syncHttpToWebView(webview: WebView): Promise<any> {
......
import {HTTPClient} from './http-client';
import * as http from 'http';
import { HTTPClient } from './http-client';
export class NullHTTPClient implements HTTPClient {
public request(options: any): Promise<any> {
return new Promise((resolve, reject) => {
resolve({ content: { toString: () => {return '';} }});
});
}
public request(options: any): Promise<any> {
return new Promise((resolve, reject) => {
resolve({ content: { toString: () => { return ''; } } });
});
}
}
export {Persistable, Serializable, DataSource} from './persistable';
export {Database} from './database';
export {Account} from './account';
export {ItemOperation} from './item-operation';
export {Loan} from './loan';
export {Hold} from './hold';
export {Portal} from './portal';
export {HTTPClient} from './http/http-client';
export {NSHTTPClient} from './http/ns-http-client';
export {NullHTTPClient} from './http/null-http-client';
export {PortalAdapter} from './portal/adapter';
export {OAuthTokenNotSet} from './portal/oauth-token-not-set';
export {WrongLoginPassword} from './portal/wrong-login-password';
export {PortalDetectionFail} from './portal/portal-detection-fail';
export {Cookies} from './cookies/cookies';
export {Manager, MBPortal} from './manager';
export {Record} from './record';
export {Item} from './item';
export {Novelties} from './novelties';
export {Card} from './card';
export {Iso8601Date} from './iso8601_date';
export {BackgroundJobs} from './background-jobs';
export {Form} from './form';
export { Persistable, Serializable, DataSource } from './persistable';
export { Database } from './database';
export { Account } from './account';
export { ItemOperation } from './item-operation';
export { Loan } from './loan';
export { Hold } from './hold';
export { Portal } from './portal';
export { HTTPClient } from './http/http-client';
export { NSHTTPClient } from './http/ns-http-client';
export { NullHTTPClient } from './http/null-http-client';
export { PortalAdapter } from './portal/adapter';
export { OAuthTokenNotSet } from './portal/oauth-token-not-set';
export { WrongLoginPassword } from './portal/wrong-login-password';
export { PortalDetectionFail } from './portal/portal-detection-fail';
export { Cookies } from './cookies/cookies';
export { Manager, MBPortal } from './manager';
export { Record } from './record';
export { Item } from './item';
export { Novelties } from './novelties';
export { Card } from './card';
export { Iso8601Date } from './iso8601_date';
export { BackgroundJobs } from './background-jobs/background-jobs';
export { Form } from './form';
import {Account, Portal, Record} from '../models/';
import { ObservableArray } from 'data/observable-array';
import { Account, Portal, Record } from '../models/';
import { ObservableArray } from 'tns-core-modules/data/observable-array';
export class Novelties extends ObservableArray<Record> {
protected _page: number = 1;
......@@ -14,14 +14,14 @@ export class Novelties extends ObservableArray<Record> {
this._account = account;
}
public loadNext(): Promise<any> {
if (this._done)
return Promise.resolve(new Array());
return this._portal
.fetchNovelties(this._account, this._page)
.then( (records) => {
.then((records) => {
this._page = this._page + 1;
this._done = (records.length == 0);
this.push(records);
......
import {Observable} from 'data/observable';
import { Observable } from 'tns-core-modules/data/observable';
export abstract class Persistable extends Observable {
protected _id: string;
protected _id: string;
constructor(id?:string) {
super();
this._id = id;
}
constructor(id?: string) {
super();
this._id = id;
}
public getId() {
return this._id;
}
public getId() {
return this._id;
}
public setId(id:string) {
this._id = id;
return this;
}
public setId(id: string) {
this._id = id;
return this;
}
public deleteChilds(datasource: DataSource): Persistable {
return this;
}
public deleteChilds(datasource: DataSource): Persistable {
return this;
}
public beforeSave(datasource: DataSource) {
}
public afterSave(datasource: DataSource) {
}
......@@ -37,23 +37,23 @@ export abstract class Persistable extends Observable {
return true;
}
public abstract serializeOn(serializable: Serializable);
public abstract materializeFrom(serializable: Serializable);
public abstract serializeOn(serializable: Serializable);
public abstract materializeFrom(serializable: Serializable);
}
export interface Serializable {
set(name, value): Serializable;
get(name, modelClass?:any): any;
serialize(): Object;
set(name, value): Serializable;
get(name, modelClass?: any): any;
serialize(): Object;
}
export interface DataSource {
delete(model: Persistable): DataSource;
delete(model: Persistable): DataSource;
save(model: Persistable): DataSource;
findAll(modelClass: any): any ;
findAll(modelClass: any): any;
}
import * as http from 'http';
import {Koha} from './koha';
import {WrongLoginPassword} from './wrong-login-password';
import {Account} from '../../models';
import { Koha } from './koha';
import { WrongLoginPassword } from './wrong-login-password';
import { Account } from '../../models';
import * as cheerio from 'cheerio';
export class KohaCas extends Koha {
public getIdentifier(): string {
public getIdentifier(): string {
return 'koha-cas';
}
......@@ -23,20 +22,20 @@ export class KohaCas extends Koha {
protected _signIn(account): Promise<any> {
if (!(account.credentials['login'] && account.credentials['password']))
return Promise.reject(new WrongLoginPassword());
account.setCookies([])
return this
.request(account,
{ url: account.getUrl() })
.then( (response) => {
{ url: account.getUrl() })
.then((response) => {
let $ = cheerio.load(response.content.toString());
let cas_link = $('a[href*="/cas/login?service="]').attr('href');
return cas_link;
})
.then( (cas_link) => {
.then((cas_link) => {
return this
.request(account, { url: cas_link })
.then( (response) => {
.then((response) => {
let $ = cheerio.load(response.content.toString());
let form_inputs = this._extractForm($);
let form_data = {
......@@ -50,20 +49,22 @@ export class KohaCas extends Koha {
return this
.request(account,
{ url: cas_link,
method: "POST",
content: this._encodeForm(form_data),
headers: {"Content-Type": "application/x-www-form-urlencoded"} });
{
url: cas_link,
method: "POST",
content: this._encodeForm(form_data),
headers: { "Content-Type": "application/x-www-form-urlencoded" }
});
})
})
.then( (response) => {
.then((response) => {
let $ = cheerio.load(response.content.toString());
let cas_link = $('a[href*="/cas/login?service="]').attr('href');
return cas_link
? this.request(account, { url: cas_link })
: response;
}).then( (response) => {
}).then((response) => {
return this._handleSignIn(account, response);
});
}
......@@ -72,7 +73,7 @@ export class KohaCas extends Koha {
protected _extractForm($): any {
let form_data = [];
$('form input')
.filter( (i, elem) => { return $(elem).attr('name') != undefined; } )
.filter((i, elem) => { return $(elem).attr('name') != undefined; })
.each((i, elem) => {
let input = $(elem)
form_data[input.attr('name')] = input.attr('value') || ''
......
declare var describe, expect, it, before, chai: any;
import {AccountsController} from '../controllers/accounts';
import {Account, Database, Manager, NullHTTPClient} from '../models/';
import {StubFrames} from './stub-frames';
import {StubDialogs} from './stub-dialogs';
import {Observable} from 'data/observable';
import { AccountsController } from '../controllers/accounts';
import { Account, Database, Manager, NullHTTPClient } from '../models/';
import { StubFrames } from './stub-frames';
import { StubDialogs } from './stub-dialogs';
import { Observable } from 'tns-core-modules/data/observable';
describe('AccountsController with one account in database', () => {
let db: Database;
......@@ -13,14 +13,16 @@ describe('AccountsController with one account in database', () => {
let account_form: Observable;
let manager: Manager;
before( () => {
before(() => {
bibliofil = (new Account())
.setLabel('Bibliofil')
.setUrl('http://mylib.fr')
.setCredentials({login: "david",
password: "guetta",
portal: "orpheemedia"});
.setCredentials({
login: "david",
password: "guetta",
portal: "orpheemedia"
});
db = Database.open('testdb').clear();
db.save(bibliofil);
......@@ -35,8 +37,10 @@ describe('AccountsController with one account in database', () => {