[{"data":1,"prerenderedAt":14811},["ShallowReactive",2],{"posts-count":3,"posts-page-2":4},50,[5,4209,6124,7023,7809,8412,9912,10430,11954,12957],{"id":6,"title":7,"author":8,"body":9,"category":4164,"categorySlug":4165,"date":4166,"description":15,"excerpt":4167,"extension":4199,"location":4200,"meta":4201,"navigation":211,"path":4202,"published":211,"seo":4203,"slug":4204,"stem":4205,"tags":4206,"timeToRead":458,"__hash__":4208},"posts\u002Fposts\u002FProgrammation\u002F2020-11-14_application_du_confinement.md","L'Application du confinement pour se déplacer","Ulrich Vandenhekke",{"type":10,"value":11,"toc":4154},"minimark",[12,16,19,30,38,41,44,47,63,68,88,91,111,114,117,120,129,132,137,144,276,279,572,575,578,581,1008,1022,1031,1034,1222,1225,1361,1365,1368,1371,1437,1448,1492,1495,1608,1611,1855,1858,2128,2131,2389,2407,2418,2422,2429,2435,2558,2572,2575,2739,2742,2748,2755,2758,3044,3047,3050,3154,3164,3174,3508,3511,3517,4073,4076,4080,4083,4098,4101,4117,4150],[13,14,15],"p",{},"Préambule: A cause du temps de validation du PlayStore, je publie cet article avec une semaine de retard.",[13,17,18],{},"Cela fait plus d'une semaine (quand j'écris ces lignes) que le re-confinement à commencé. Quand je vais courir, je dois me cantonner à\n1km autour de chez moi. Mais quand je cours j'aimerais que mon téléphone intelligent me prévienne quand j'approche du rayon de 1km ou\nquand je le dépasse. Je ne souhaite pas avoir le nez sur une carte de mon téléphone.",[13,20,21,22,29],{},"Je regarde ce qui se fait. J'ai trouvé l'application suivante sur le play store : ",[23,24,28],"a",{"href":25,"rel":26},"https:\u002F\u002Fplay.google.com\u002Fstore\u002Fapps\u002Fdetails?id=com.BETechnology.unkm",[27],"nofollow","1km",".\nL'application m'avait l'air de répondre à mes critères mais ne fonctionnait pas lors de mon utilisation (en plus il y avait de la pub).",[13,31,32,33,37],{},"Une autre application ",[23,34,28],{"href":35,"rel":36},"https:\u002F\u002Fplay.google.com\u002Fstore\u002Fapps\u002Fdetails?id=com.apps100101.a1km",[27]," pourrait répondre à mon besoin mais\nje ne l'ai pas testé.",[13,39,40],{},"Beaucoup d'applications ont pour but de dessiner un cercle sur une carte.",[13,42,43],{},"J'ai finalement décidé d'écrire ma propre application (en plus elle sera open source).",[13,45,46],{},"Voici ce dont j'ai besoin:",[48,49,50,54,57,60],"ul",{},[51,52,53],"li",{},"une application open source",[51,55,56],{},"un gros bouton pour démarrer la surveillance (à partir de mon point de départ)",[51,58,59],{},"un avertissement quand on approche du rayon max des 1km.",[51,61,62],{},"un avertissement régulier qand on dépasse les 1km.",[64,65,67],"h2",{"id":66},"lapplication","L'application",[13,69,70,71,75,76,79,80,83,84,87],{},"Pour développer une application Android je vais démarrer ",[72,73,74],"em",{},"Android Studio"," et commencer à développer le 1er écran. Je choisis le\nlanguage ",[72,77,78],{},"Java"," que je maîtrise plus que le language ",[72,81,82],{},"Kotlin"," et le SDK Minimum de ",[72,85,86],{},"Android 6.0"," pour toucher 85% des\nutilisateurs (si l'application peut interesser d'autres personnes).",[13,89,90],{},"J'imagine l'application découpée en deux parties:",[48,92,93,108],{},[51,94,95,96,107],{},"L'activité",[97,98,99],"sup",{},[23,100,106],{"href":101,"ariaDescribedBy":102,"dataFootnoteRef":104,"id":105},"#user-content-fn-1",[103],"footnote-label","","user-content-fnref-1","1"," principale de l'application qui contiendra mon gros bouton, la position de départ, la position courante, et la\ndistance à vol d'oiseau du point de départ.",[51,109,110],{},"Un service, dont le but est quand l'application est démarrée, de surveiller les déplacements et d'informer l'utilisateur.",[13,112,113],{},"Nous allons donc commencer par développer l'activité",[64,115,95],{"id":116},"lactivité",[13,118,119],{},"Je ne suis pas graphiste ni UI\u002FUX designer. Le design de cette première interface va alors être très simple et très sobre. Un\ngros bouton + les différentes informations dont j'ai besoin quand l'application est démarrée :",[13,121,122],{},[123,124],"img",{"alt":125,"className":126,"src":128},"Screenshoot page principale",[127],"img-center","\u002FProgrammation\u002F1kmarround\u002Fscreenshoot1.png",[13,130,131],{},"Lors du démarrage de l'application, j'ai besoin que celle-ci écoute le changement, les positions de l'utilisateur afin de\ndéfinir le point de départ. Comme ce service d'écoute me sera utile également pour le service, je développe une classe à coté.",[133,134,136],"h3",{"id":135},"arroundlocationmanager","ArroundLocationManager",[13,138,139,140,143],{},"Voici donc la classe ",[141,142,136],"strong",{},":",[145,146,150],"pre",{"className":147,"code":148,"language":149,"meta":104,"style":104},"language-java shiki shiki-themes one-dark-pro","class ArroundLocationManager extends Thread {\n    private static final String TAG = \"ArroundLocationManager\";\n\n    private LocationManager mLocationManager = null;\n    private static final int LOCATION_INTERVAL = 1000;\n    private static final float LOCATION_DISTANCE = 50f;\n","java",[151,152,153,176,206,213,232,254],"code",{"__ignoreMap":104},[154,155,158,162,166,169,172],"span",{"class":156,"line":157},"line",1,[154,159,161],{"class":160},"seHd6","class",[154,163,165],{"class":164},"sU0A5"," ArroundLocationManager",[154,167,168],{"class":160}," extends",[154,170,171],{"class":164}," Thread",[154,173,175],{"class":174},"sn6KH"," {\n",[154,177,179,182,185,188,191,195,199,203],{"class":156,"line":178},2,[154,180,181],{"class":160},"    private",[154,183,184],{"class":160}," static",[154,186,187],{"class":160}," final",[154,189,190],{"class":164}," String",[154,192,194],{"class":193},"sVyAn"," TAG ",[154,196,198],{"class":197},"sjrmR","=",[154,200,202],{"class":201},"subq3"," \"ArroundLocationManager\"",[154,204,205],{"class":174},";\n",[154,207,209],{"class":156,"line":208},3,[154,210,212],{"emptyLinePlaceholder":211},true,"\n",[154,214,216,218,221,224,226,230],{"class":156,"line":215},4,[154,217,181],{"class":160},[154,219,220],{"class":164}," LocationManager",[154,222,223],{"class":193}," mLocationManager ",[154,225,198],{"class":197},[154,227,229],{"class":228},"sVC51"," null",[154,231,205],{"class":174},[154,233,235,237,239,241,244,247,249,252],{"class":156,"line":234},5,[154,236,181],{"class":160},[154,238,184],{"class":160},[154,240,187],{"class":160},[154,242,243],{"class":160}," int",[154,245,246],{"class":193}," LOCATION_INTERVAL ",[154,248,198],{"class":197},[154,250,251],{"class":228}," 1000",[154,253,205],{"class":174},[154,255,257,259,261,263,266,269,271,274],{"class":156,"line":256},6,[154,258,181],{"class":160},[154,260,184],{"class":160},[154,262,187],{"class":160},[154,264,265],{"class":160}," float",[154,267,268],{"class":193}," LOCATION_DISTANCE ",[154,270,198],{"class":197},[154,272,273],{"class":228}," 50f",[154,275,205],{"class":174},[13,277,278],{},"Pour commencer définissons quelques constantes: je souhaite avoir la position, tous les 50m et au maximum toutes les secondes\n(m'enfin quelqu'un qui fait plus de 50m en une seconde à pied est trop fort pour moi).",[145,280,282],{"className":147,"code":281,"language":149,"meta":104,"style":104},"    private List\u003CArroundLocationManager.ArrroundLocationListener> listener = new ArrayList\u003C>();\n\n    public interface ArrroundLocationListener {\n        void updateLocation(Location startLocation);\n    }\n\n    private ArroundLocationManager.LocationListener[] mLocationListeners = new ArroundLocationManager.LocationListener[]{\n            new ArroundLocationManager.LocationListener(LocationManager.GPS_PROVIDER),\n            new ArroundLocationManager.LocationListener(LocationManager.NETWORK_PROVIDER)\n    };\n\n    public void addListener(ArroundLocationManager.ArrroundLocationListener l) {\n        listener.add(l);\n    }\n\n    private void callListener(Location location) {\n        for( ArroundLocationManager.ArrroundLocationListener l : listener) {\n            l.updateLocation(location);\n        }\n    }\n",[151,283,284,324,328,341,363,368,372,400,425,448,456,461,483,497,502,507,524,547,561,567],{"__ignoreMap":104},[154,285,286,288,291,294,296,299,302,305,308,310,313,316,319,322],{"class":156,"line":157},[154,287,181],{"class":160},[154,289,290],{"class":164}," List",[154,292,293],{"class":174},"\u003C",[154,295,136],{"class":164},[154,297,298],{"class":174},".",[154,300,301],{"class":164},"ArrroundLocationListener",[154,303,304],{"class":174},">",[154,306,307],{"class":193}," listener ",[154,309,198],{"class":197},[154,311,312],{"class":160}," new",[154,314,315],{"class":164}," ArrayList",[154,317,318],{"class":174},"\u003C>",[154,320,321],{"class":193},"()",[154,323,205],{"class":174},[154,325,326],{"class":156,"line":178},[154,327,212],{"emptyLinePlaceholder":211},[154,329,330,333,336,339],{"class":156,"line":208},[154,331,332],{"class":160},"    public",[154,334,335],{"class":160}," interface",[154,337,338],{"class":164}," ArrroundLocationListener",[154,340,175],{"class":174},[154,342,343,346,350,353,356,360],{"class":156,"line":215},[154,344,345],{"class":160},"        void",[154,347,349],{"class":348},"sVbv2"," updateLocation",[154,351,352],{"class":174},"(",[154,354,355],{"class":164},"Location",[154,357,359],{"class":358},"s_ZVi"," startLocation",[154,361,362],{"class":174},");\n",[154,364,365],{"class":156,"line":234},[154,366,367],{"class":174},"    }\n",[154,369,370],{"class":156,"line":256},[154,371,212],{"emptyLinePlaceholder":211},[154,373,375,377,379,381,384,387,389,391,393,395,397],{"class":156,"line":374},7,[154,376,181],{"class":160},[154,378,165],{"class":164},[154,380,298],{"class":174},[154,382,383],{"class":164},"LocationListener",[154,385,386],{"class":193},"[] mLocationListeners ",[154,388,198],{"class":197},[154,390,312],{"class":160},[154,392,165],{"class":164},[154,394,298],{"class":174},[154,396,383],{"class":164},[154,398,399],{"class":193},"[]{\n",[154,401,403,406,408,410,412,414,417,419,422],{"class":156,"line":402},8,[154,404,405],{"class":160},"            new",[154,407,165],{"class":193},[154,409,298],{"class":174},[154,411,383],{"class":348},[154,413,352],{"class":174},[154,415,416],{"class":164},"LocationManager",[154,418,298],{"class":174},[154,420,421],{"class":164},"GPS_PROVIDER",[154,423,424],{"class":174},"),\n",[154,426,428,430,432,434,436,438,440,442,445],{"class":156,"line":427},9,[154,429,405],{"class":160},[154,431,165],{"class":193},[154,433,298],{"class":174},[154,435,383],{"class":348},[154,437,352],{"class":174},[154,439,416],{"class":164},[154,441,298],{"class":174},[154,443,444],{"class":164},"NETWORK_PROVIDER",[154,446,447],{"class":174},")\n",[154,449,451,454],{"class":156,"line":450},10,[154,452,453],{"class":193},"    }",[154,455,205],{"class":174},[154,457,459],{"class":156,"line":458},11,[154,460,212],{"emptyLinePlaceholder":211},[154,462,464,466,469,472,474,476,478,480],{"class":156,"line":463},12,[154,465,332],{"class":160},[154,467,468],{"class":160}," void",[154,470,471],{"class":348}," addListener",[154,473,352],{"class":193},[154,475,136],{"class":164},[154,477,298],{"class":174},[154,479,301],{"class":164},[154,481,482],{"class":193}," l) {\n",[154,484,486,489,491,494],{"class":156,"line":485},13,[154,487,488],{"class":164},"        listener",[154,490,298],{"class":174},[154,492,493],{"class":348},"add",[154,495,496],{"class":174},"(l);\n",[154,498,500],{"class":156,"line":499},14,[154,501,367],{"class":193},[154,503,505],{"class":156,"line":504},15,[154,506,212],{"emptyLinePlaceholder":211},[154,508,510,512,514,517,519,521],{"class":156,"line":509},16,[154,511,181],{"class":160},[154,513,468],{"class":160},[154,515,516],{"class":348}," callListener",[154,518,352],{"class":193},[154,520,355],{"class":164},[154,522,523],{"class":193}," location) {\n",[154,525,527,530,533,535,537,539,542,544],{"class":156,"line":526},17,[154,528,529],{"class":160},"        for",[154,531,532],{"class":193},"( ",[154,534,136],{"class":164},[154,536,298],{"class":174},[154,538,301],{"class":164},[154,540,541],{"class":193}," l ",[154,543,143],{"class":160},[154,545,546],{"class":193}," listener) {\n",[154,548,550,553,555,558],{"class":156,"line":549},18,[154,551,552],{"class":164},"            l",[154,554,298],{"class":174},[154,556,557],{"class":348},"updateLocation",[154,559,560],{"class":174},"(location);\n",[154,562,564],{"class":156,"line":563},19,[154,565,566],{"class":193},"        }\n",[154,568,570],{"class":156,"line":569},20,[154,571,367],{"class":193},[13,573,574],{},"Viens ensuite la définition d'un listener pour que les applications qui s'abonnent à cette classe puissent bénéficier d'un\nlistener et recevoir des notifications lors de la mise à jour des positions.",[13,576,577],{},"Comme on peut le constater je fais tourner cette classe dans un thread. Lors de mon développement je me suis rendu compte que\nlorsque je quittais l'application, le service était tué également. Une des raisons à cela est que le service est dans le même\nthread que l'activité principale. L'ajout de ce thread (ainsi que d'autre chose) ont résolu le problème. (Mais il est possible\nque ce soit plus lié aux autres choses qu'au thread lui même).",[13,579,580],{},"Voici le coeur du thread:",[145,582,584],{"className":147,"code":583,"language":149,"meta":104,"style":104},"    public void run() {\n        Looper.prepare();\n\n        initializeLocationManager();\n\n        Looper.loop();\n\n        \u002F\u002F Never called, loop is killed when activity or thread stopped\n        finalizeLocationManager();\n    }\n\n    private ArroundLocationManager.LocationListener[] mLocationListeners = new ArroundLocationManager.LocationListener[]{\n        new ArroundLocationManager.LocationListener(LocationManager.GPS_PROVIDER),\n        new ArroundLocationManager.LocationListener(LocationManager.NETWORK_PROVIDER)\n    };\n\n    public void initializeLocationManager() {\n        try {\n            mLocationManager.requestLocationUpdates(\n                    LocationManager.NETWORK_PROVIDER, LOCATION_INTERVAL, LOCATION_DISTANCE,\n                    mLocationListeners[1]);\n        } catch (java.lang.SecurityException ex) {\n            Log.i(TAG, \"fail to request location update, ignore\", ex);\n        } catch (IllegalArgumentException ex) {\n            Log.d(TAG, \"network provider does not exist, \" + ex.getMessage());\n        }\n\n        try {\n            mLocationManager.requestLocationUpdates(\n                    LocationManager.GPS_PROVIDER, LOCATION_INTERVAL, LOCATION_DISTANCE,\n                    mLocationListeners[0]);\n        } catch (java.lang.SecurityException ex) {\n            Log.i(TAG, \"fail to request location update, ignore\", ex);\n        } catch (IllegalArgumentException ex) {\n            Log.d(TAG, \"gps provider does not exist \" + ex.getMessage());\n        }\n    }\n",[151,585,586,598,611,615,624,628,639,643,649,658,662,666,690,711,731,737,741,752,759,772,784,795,816,836,852,880,885,890,897,908,919,929,944,959,974,998,1003],{"__ignoreMap":104},[154,587,588,590,592,595],{"class":156,"line":157},[154,589,332],{"class":160},[154,591,468],{"class":160},[154,593,594],{"class":348}," run",[154,596,597],{"class":193},"() {\n",[154,599,600,603,605,608],{"class":156,"line":178},[154,601,602],{"class":164},"        Looper",[154,604,298],{"class":174},[154,606,607],{"class":348},"prepare",[154,609,610],{"class":174},"();\n",[154,612,613],{"class":156,"line":208},[154,614,212],{"emptyLinePlaceholder":211},[154,616,617,620,622],{"class":156,"line":215},[154,618,619],{"class":348},"        initializeLocationManager",[154,621,321],{"class":193},[154,623,205],{"class":174},[154,625,626],{"class":156,"line":234},[154,627,212],{"emptyLinePlaceholder":211},[154,629,630,632,634,637],{"class":156,"line":256},[154,631,602],{"class":164},[154,633,298],{"class":174},[154,635,636],{"class":348},"loop",[154,638,610],{"class":174},[154,640,641],{"class":156,"line":374},[154,642,212],{"emptyLinePlaceholder":211},[154,644,645],{"class":156,"line":402},[154,646,648],{"class":647},"sV9Aq","        \u002F\u002F Never called, loop is killed when activity or thread stopped\n",[154,650,651,654,656],{"class":156,"line":427},[154,652,653],{"class":348},"        finalizeLocationManager",[154,655,321],{"class":193},[154,657,205],{"class":174},[154,659,660],{"class":156,"line":450},[154,661,367],{"class":193},[154,663,664],{"class":156,"line":458},[154,665,212],{"emptyLinePlaceholder":211},[154,667,668,670,672,674,676,678,680,682,684,686,688],{"class":156,"line":463},[154,669,181],{"class":160},[154,671,165],{"class":164},[154,673,298],{"class":174},[154,675,383],{"class":164},[154,677,386],{"class":193},[154,679,198],{"class":197},[154,681,312],{"class":160},[154,683,165],{"class":164},[154,685,298],{"class":174},[154,687,383],{"class":164},[154,689,399],{"class":193},[154,691,692,695,697,699,701,703,705,707,709],{"class":156,"line":485},[154,693,694],{"class":160},"        new",[154,696,165],{"class":193},[154,698,298],{"class":174},[154,700,383],{"class":348},[154,702,352],{"class":174},[154,704,416],{"class":164},[154,706,298],{"class":174},[154,708,421],{"class":164},[154,710,424],{"class":174},[154,712,713,715,717,719,721,723,725,727,729],{"class":156,"line":499},[154,714,694],{"class":160},[154,716,165],{"class":193},[154,718,298],{"class":174},[154,720,383],{"class":348},[154,722,352],{"class":174},[154,724,416],{"class":164},[154,726,298],{"class":174},[154,728,444],{"class":164},[154,730,447],{"class":174},[154,732,733,735],{"class":156,"line":504},[154,734,453],{"class":193},[154,736,205],{"class":174},[154,738,739],{"class":156,"line":509},[154,740,212],{"emptyLinePlaceholder":211},[154,742,743,745,747,750],{"class":156,"line":526},[154,744,332],{"class":160},[154,746,468],{"class":160},[154,748,749],{"class":348}," initializeLocationManager",[154,751,597],{"class":193},[154,753,754,757],{"class":156,"line":549},[154,755,756],{"class":160},"        try",[154,758,175],{"class":193},[154,760,761,764,766,769],{"class":156,"line":563},[154,762,763],{"class":164},"            mLocationManager",[154,765,298],{"class":174},[154,767,768],{"class":348},"requestLocationUpdates",[154,770,771],{"class":174},"(\n",[154,773,774,777,779,781],{"class":156,"line":569},[154,775,776],{"class":164},"                    LocationManager",[154,778,298],{"class":174},[154,780,444],{"class":164},[154,782,783],{"class":174},", LOCATION_INTERVAL, LOCATION_DISTANCE,\n",[154,785,787,790,792],{"class":156,"line":786},21,[154,788,789],{"class":174},"                    mLocationListeners[",[154,791,106],{"class":228},[154,793,794],{"class":174},"]);\n",[154,796,798,801,804,807,810,813],{"class":156,"line":797},22,[154,799,800],{"class":193},"        } ",[154,802,803],{"class":160},"catch",[154,805,806],{"class":193}," (",[154,808,809],{"class":164},"java.lang.SecurityException",[154,811,812],{"class":358}," ex",[154,814,815],{"class":193},") {\n",[154,817,819,822,824,827,830,833],{"class":156,"line":818},23,[154,820,821],{"class":164},"            Log",[154,823,298],{"class":174},[154,825,826],{"class":348},"i",[154,828,829],{"class":174},"(TAG, ",[154,831,832],{"class":201},"\"fail to request location update, ignore\"",[154,834,835],{"class":174},", ex);\n",[154,837,839,841,843,845,848,850],{"class":156,"line":838},24,[154,840,800],{"class":193},[154,842,803],{"class":160},[154,844,806],{"class":193},[154,846,847],{"class":164},"IllegalArgumentException",[154,849,812],{"class":358},[154,851,815],{"class":193},[154,853,855,857,859,862,864,867,870,872,874,877],{"class":156,"line":854},25,[154,856,821],{"class":164},[154,858,298],{"class":174},[154,860,861],{"class":348},"d",[154,863,829],{"class":174},[154,865,866],{"class":201},"\"network provider does not exist, \"",[154,868,869],{"class":197}," +",[154,871,812],{"class":164},[154,873,298],{"class":174},[154,875,876],{"class":348},"getMessage",[154,878,879],{"class":174},"());\n",[154,881,883],{"class":156,"line":882},26,[154,884,566],{"class":193},[154,886,888],{"class":156,"line":887},27,[154,889,212],{"emptyLinePlaceholder":211},[154,891,893,895],{"class":156,"line":892},28,[154,894,756],{"class":160},[154,896,175],{"class":193},[154,898,900,902,904,906],{"class":156,"line":899},29,[154,901,763],{"class":164},[154,903,298],{"class":174},[154,905,768],{"class":348},[154,907,771],{"class":174},[154,909,911,913,915,917],{"class":156,"line":910},30,[154,912,776],{"class":164},[154,914,298],{"class":174},[154,916,421],{"class":164},[154,918,783],{"class":174},[154,920,922,924,927],{"class":156,"line":921},31,[154,923,789],{"class":174},[154,925,926],{"class":228},"0",[154,928,794],{"class":174},[154,930,932,934,936,938,940,942],{"class":156,"line":931},32,[154,933,800],{"class":193},[154,935,803],{"class":160},[154,937,806],{"class":193},[154,939,809],{"class":164},[154,941,812],{"class":358},[154,943,815],{"class":193},[154,945,947,949,951,953,955,957],{"class":156,"line":946},33,[154,948,821],{"class":164},[154,950,298],{"class":174},[154,952,826],{"class":348},[154,954,829],{"class":174},[154,956,832],{"class":201},[154,958,835],{"class":174},[154,960,962,964,966,968,970,972],{"class":156,"line":961},34,[154,963,800],{"class":193},[154,965,803],{"class":160},[154,967,806],{"class":193},[154,969,847],{"class":164},[154,971,812],{"class":358},[154,973,815],{"class":193},[154,975,977,979,981,983,985,988,990,992,994,996],{"class":156,"line":976},35,[154,978,821],{"class":164},[154,980,298],{"class":174},[154,982,861],{"class":348},[154,984,829],{"class":174},[154,986,987],{"class":201},"\"gps provider does not exist \"",[154,989,869],{"class":197},[154,991,812],{"class":164},[154,993,298],{"class":174},[154,995,876],{"class":348},[154,997,879],{"class":174},[154,999,1001],{"class":156,"line":1000},36,[154,1002,566],{"class":193},[154,1004,1006],{"class":156,"line":1005},37,[154,1007,367],{"class":193},[13,1009,1010,1011,1013,1014,1017,1018,1021],{},"On utilise le service ",[151,1012,416],{}," d'android pour écouter la position de l'utilisateur. On écoute la position venant\ndu ",[72,1015,1016],{},"Network"," qui permet d'avoir une position moins fiable mais rapide, puis celle venant du ",[72,1019,1020],{},"GPS"," permettant d'avoir une\nposition fiable (mais lente à obtenir).",[13,1023,1024,1025,1030],{},"Afin d'avoir une position la plus précise aussi, je me suis inspiré du code suivant:\n",[23,1026,1029],{"href":1027,"rel":1028},"https:\u002F\u002Fstuff.mit.edu\u002Fafs\u002Fsipb\u002Fproject\u002Fandroid\u002Fdocs\u002Ftraining\u002Fbasics\u002Flocation\u002Fcurrentlocation.html",[27],"Obtaining the Current Location",".\nLe code en question permet de choisir entre deux positions la plus précise (entre la position NETWORK et la position GPS).",[13,1032,1033],{},"On notifie les appelants:",[145,1035,1037],{"className":147,"code":1036,"language":149,"meta":104,"style":104},"    private class LocationListener implements android.location.LocationListener {\n        public LocationListener(String provider) {\n            Log.e(TAG, \"LocationListener \" + provider);\n            mLocation = new Location(provider);\n        }\n\n        @Override\n        public void onLocationChanged(Location location) {\n            Log.e(TAG, \"onLocationChanged: \" + location);\n            if (isBetterLocation(location, mLocation)) {\n                mLocation.set(location);\n            }\n\n            callListener(mLocation);\n        }\n        ...\n    }\n",[151,1038,1039,1059,1079,1098,1113,1117,1121,1129,1149,1167,1180,1192,1197,1201,1209,1213,1218],{"__ignoreMap":104},[154,1040,1041,1043,1046,1049,1052,1055,1057],{"class":156,"line":157},[154,1042,181],{"class":160},[154,1044,1045],{"class":160}," class",[154,1047,1048],{"class":164}," LocationListener",[154,1050,1051],{"class":160}," implements",[154,1053,1054],{"class":193}," android.location.",[154,1056,383],{"class":164},[154,1058,175],{"class":174},[154,1060,1061,1064,1066,1068,1071,1074,1077],{"class":156,"line":178},[154,1062,1063],{"class":160},"        public",[154,1065,1048],{"class":348},[154,1067,352],{"class":174},[154,1069,1070],{"class":164},"String",[154,1072,1073],{"class":358}," provider",[154,1075,1076],{"class":174},")",[154,1078,175],{"class":174},[154,1080,1081,1083,1085,1088,1090,1093,1095],{"class":156,"line":208},[154,1082,821],{"class":164},[154,1084,298],{"class":174},[154,1086,1087],{"class":348},"e",[154,1089,829],{"class":174},[154,1091,1092],{"class":201},"\"LocationListener \"",[154,1094,869],{"class":197},[154,1096,1097],{"class":174}," provider);\n",[154,1099,1100,1103,1105,1107,1110],{"class":156,"line":215},[154,1101,1102],{"class":174},"            mLocation ",[154,1104,198],{"class":197},[154,1106,312],{"class":160},[154,1108,1109],{"class":348}," Location",[154,1111,1112],{"class":174},"(provider);\n",[154,1114,1115],{"class":156,"line":234},[154,1116,566],{"class":174},[154,1118,1119],{"class":156,"line":256},[154,1120,212],{"emptyLinePlaceholder":211},[154,1122,1123,1126],{"class":156,"line":374},[154,1124,1125],{"class":174},"        @",[154,1127,1128],{"class":164},"Override\n",[154,1130,1131,1133,1135,1138,1140,1142,1145,1147],{"class":156,"line":402},[154,1132,1063],{"class":160},[154,1134,468],{"class":160},[154,1136,1137],{"class":348}," onLocationChanged",[154,1139,352],{"class":174},[154,1141,355],{"class":164},[154,1143,1144],{"class":358}," location",[154,1146,1076],{"class":174},[154,1148,175],{"class":174},[154,1150,1151,1153,1155,1157,1159,1162,1164],{"class":156,"line":427},[154,1152,821],{"class":164},[154,1154,298],{"class":174},[154,1156,1087],{"class":348},[154,1158,829],{"class":174},[154,1160,1161],{"class":201},"\"onLocationChanged: \"",[154,1163,869],{"class":197},[154,1165,1166],{"class":174}," location);\n",[154,1168,1169,1172,1174,1177],{"class":156,"line":450},[154,1170,1171],{"class":160},"            if",[154,1173,806],{"class":174},[154,1175,1176],{"class":348},"isBetterLocation",[154,1178,1179],{"class":174},"(location, mLocation)) {\n",[154,1181,1182,1185,1187,1190],{"class":156,"line":458},[154,1183,1184],{"class":164},"                mLocation",[154,1186,298],{"class":174},[154,1188,1189],{"class":348},"set",[154,1191,560],{"class":174},[154,1193,1194],{"class":156,"line":463},[154,1195,1196],{"class":174},"            }\n",[154,1198,1199],{"class":156,"line":485},[154,1200,212],{"emptyLinePlaceholder":211},[154,1202,1203,1206],{"class":156,"line":499},[154,1204,1205],{"class":348},"            callListener",[154,1207,1208],{"class":174},"(mLocation);\n",[154,1210,1211],{"class":156,"line":504},[154,1212,566],{"class":174},[154,1214,1215],{"class":156,"line":509},[154,1216,1217],{"class":174},"        ...\n",[154,1219,1220],{"class":156,"line":526},[154,1221,367],{"class":174},[13,1223,1224],{},"Enfin on a une méthode pour calculer les distances avec Android.:",[145,1226,1228],{"className":147,"code":1227,"language":149,"meta":104,"style":104},"    public static float getDistance(Location startLocation, Location lastLocation) {\n        float[] results = new float[1];\n        Location.distanceBetween(startLocation.getLatitude(), startLocation.getLongitude(), lastLocation.getLatitude(), lastLocation.getLongitude(), results);\n        float distance = results[0];\n        return distance;\n    }\n",[151,1229,1230,1255,1279,1329,1347,1357],{"__ignoreMap":104},[154,1231,1232,1234,1236,1238,1241,1243,1245,1247,1250,1252],{"class":156,"line":157},[154,1233,332],{"class":160},[154,1235,184],{"class":160},[154,1237,265],{"class":160},[154,1239,1240],{"class":348}," getDistance",[154,1242,352],{"class":193},[154,1244,355],{"class":164},[154,1246,359],{"class":193},[154,1248,1249],{"class":174},",",[154,1251,1109],{"class":164},[154,1253,1254],{"class":193}," lastLocation) {\n",[154,1256,1257,1260,1263,1265,1267,1269,1272,1274,1277],{"class":156,"line":178},[154,1258,1259],{"class":160},"        float",[154,1261,1262],{"class":193},"[] results ",[154,1264,198],{"class":197},[154,1266,312],{"class":160},[154,1268,265],{"class":160},[154,1270,1271],{"class":193},"[",[154,1273,106],{"class":228},[154,1275,1276],{"class":193},"]",[154,1278,205],{"class":174},[154,1280,1281,1284,1286,1289,1291,1294,1296,1299,1302,1304,1306,1309,1311,1314,1316,1318,1320,1322,1324,1326],{"class":156,"line":208},[154,1282,1283],{"class":164},"        Location",[154,1285,298],{"class":174},[154,1287,1288],{"class":348},"distanceBetween",[154,1290,352],{"class":174},[154,1292,1293],{"class":164},"startLocation",[154,1295,298],{"class":174},[154,1297,1298],{"class":348},"getLatitude",[154,1300,1301],{"class":174},"(), ",[154,1303,1293],{"class":164},[154,1305,298],{"class":174},[154,1307,1308],{"class":348},"getLongitude",[154,1310,1301],{"class":174},[154,1312,1313],{"class":164},"lastLocation",[154,1315,298],{"class":174},[154,1317,1298],{"class":348},[154,1319,1301],{"class":174},[154,1321,1313],{"class":164},[154,1323,298],{"class":174},[154,1325,1308],{"class":348},[154,1327,1328],{"class":174},"(), results);\n",[154,1330,1331,1333,1336,1338,1341,1343,1345],{"class":156,"line":215},[154,1332,1259],{"class":160},[154,1334,1335],{"class":193}," distance ",[154,1337,198],{"class":197},[154,1339,1340],{"class":193}," results[",[154,1342,926],{"class":228},[154,1344,1276],{"class":193},[154,1346,205],{"class":174},[154,1348,1349,1352,1355],{"class":156,"line":234},[154,1350,1351],{"class":160},"        return",[154,1353,1354],{"class":193}," distance",[154,1356,205],{"class":174},[154,1358,1359],{"class":156,"line":256},[154,1360,367],{"class":193},[133,1362,1364],{"id":1363},"mainactivity","MainActivity",[13,1366,1367],{},"Retournons dans notre activité principale. Je passe la création du layout qui est fort simple.",[13,1369,1370],{},"Dans l'activité, nous allons commencer par implémenter la phase de création du cycle de vie de notre activité:",[145,1372,1374],{"className":147,"code":1373,"language":149,"meta":104,"style":104},"    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n",[151,1375,1376,1383,1400,1413],{"__ignoreMap":104},[154,1377,1378,1381],{"class":156,"line":157},[154,1379,1380],{"class":174},"    @",[154,1382,1128],{"class":164},[154,1384,1385,1387,1389,1392,1394,1397],{"class":156,"line":178},[154,1386,332],{"class":160},[154,1388,468],{"class":160},[154,1390,1391],{"class":348}," onCreate",[154,1393,352],{"class":193},[154,1395,1396],{"class":164},"Bundle",[154,1398,1399],{"class":193}," savedInstanceState) {\n",[154,1401,1402,1405,1407,1410],{"class":156,"line":208},[154,1403,1404],{"class":164},"        super",[154,1406,298],{"class":174},[154,1408,1409],{"class":348},"onCreate",[154,1411,1412],{"class":174},"(savedInstanceState);\n",[154,1414,1415,1418,1420,1423,1425,1428,1430,1433,1435],{"class":156,"line":215},[154,1416,1417],{"class":348},"        setContentView",[154,1419,352],{"class":193},[154,1421,1422],{"class":164},"R",[154,1424,298],{"class":174},[154,1426,1427],{"class":164},"layout",[154,1429,298],{"class":174},[154,1431,1432],{"class":164},"activity_main",[154,1434,1076],{"class":193},[154,1436,205],{"class":174},[13,1438,1439,1440,298],{},"Depuis Android 6.0, il faut demander à l'utilisateur la permission d'utiliser la position de l'utilisateur. Du coup\non commence par demander la permission à l'utilisateur d'avoir accès à sa position",[97,1441,1442],{},[23,1443,1447],{"href":1444,"ariaDescribedBy":1445,"dataFootnoteRef":104,"id":1446},"#user-content-fn-2",[103],"user-content-fnref-2","2",[145,1449,1451],{"className":147,"code":1450,"language":149,"meta":104,"style":104},"        requestPermissionsIfNecessary(new String[]{\n                Manifest.permission.ACCESS_FINE_LOCATION,\n        });\n",[151,1452,1453,1467,1485],{"__ignoreMap":104},[154,1454,1455,1458,1460,1463,1465],{"class":156,"line":157},[154,1456,1457],{"class":348},"        requestPermissionsIfNecessary",[154,1459,352],{"class":193},[154,1461,1462],{"class":160},"new",[154,1464,190],{"class":164},[154,1466,399],{"class":193},[154,1468,1469,1472,1474,1477,1479,1482],{"class":156,"line":178},[154,1470,1471],{"class":164},"                Manifest",[154,1473,298],{"class":174},[154,1475,1476],{"class":164},"permission",[154,1478,298],{"class":174},[154,1480,1481],{"class":164},"ACCESS_FINE_LOCATION",[154,1483,1484],{"class":174},",\n",[154,1486,1487,1490],{"class":156,"line":208},[154,1488,1489],{"class":193},"        })",[154,1491,205],{"class":174},[13,1493,1494],{},"Ensuite on appelle notre location manager et on écoute les changements de positions. Pour chaque changement de position\non met à jour la position et on met à jour les textes.",[145,1496,1498],{"className":147,"code":1497,"language":149,"meta":104,"style":104},"        locationManager = new ArroundLocationManager(this);\n        locationManager.addListener((location) -> {\n            if (!isMyServiceRunning(ArroundService.class)) {\n                startLocation = location;\n            }\n            MainActivity.this.updateTextLocation();\n        });\n        locationManager.start();\n",[151,1499,1500,1520,1538,1562,1572,1576,1592,1597],{"__ignoreMap":104},[154,1501,1502,1505,1507,1509,1511,1513,1516,1518],{"class":156,"line":157},[154,1503,1504],{"class":193},"        locationManager ",[154,1506,198],{"class":197},[154,1508,312],{"class":160},[154,1510,165],{"class":348},[154,1512,352],{"class":193},[154,1514,1515],{"class":164},"this",[154,1517,1076],{"class":193},[154,1519,205],{"class":174},[154,1521,1522,1525,1527,1530,1533,1536],{"class":156,"line":178},[154,1523,1524],{"class":164},"        locationManager",[154,1526,298],{"class":174},[154,1528,1529],{"class":348},"addListener",[154,1531,1532],{"class":174},"((location) ",[154,1534,1535],{"class":160},"->",[154,1537,175],{"class":174},[154,1539,1540,1542,1544,1547,1550,1552,1555,1557,1559],{"class":156,"line":208},[154,1541,1171],{"class":160},[154,1543,806],{"class":174},[154,1545,1546],{"class":197},"!",[154,1548,1549],{"class":348},"isMyServiceRunning",[154,1551,352],{"class":174},[154,1553,1554],{"class":164},"ArroundService",[154,1556,298],{"class":174},[154,1558,161],{"class":164},[154,1560,1561],{"class":174},")) {\n",[154,1563,1564,1567,1569],{"class":156,"line":215},[154,1565,1566],{"class":174},"                startLocation ",[154,1568,198],{"class":197},[154,1570,1571],{"class":174}," location;\n",[154,1573,1574],{"class":156,"line":234},[154,1575,1196],{"class":174},[154,1577,1578,1581,1583,1585,1587,1590],{"class":156,"line":256},[154,1579,1580],{"class":164},"            MainActivity",[154,1582,298],{"class":174},[154,1584,1515],{"class":164},[154,1586,298],{"class":174},[154,1588,1589],{"class":348},"updateTextLocation",[154,1591,610],{"class":174},[154,1593,1594],{"class":156,"line":374},[154,1595,1596],{"class":174},"        });\n",[154,1598,1599,1601,1603,1606],{"class":156,"line":402},[154,1600,1524],{"class":164},[154,1602,298],{"class":174},[154,1604,1605],{"class":348},"start",[154,1607,610],{"class":174},[13,1609,1610],{},"Enfin on initialise l'IHM.",[145,1612,1614],{"className":147,"code":1613,"language":149,"meta":104,"style":104},"        distanceText = findViewById(R.id.distance);\n        startText = findViewById(R.id.start);\n        locationText = findViewById(R.id.location);\n\n        goButton = findViewById(R.id.goButton);\n        stopButton = findViewById(R.id.stopButton);\n        goButton.setOnClickListener(v -> {\n            setStart(true);\n        });\n        stopButton.setOnClickListener(v -> {\n            setStart(false);\n        });\n        setStart(isMyServiceRunning(ArroundService.class));\n        MainActivity.this.updateTextLocation();\n    }\n",[151,1615,1616,1644,1669,1695,1699,1725,1751,1768,1780,1784,1799,1810,1814,1836,1851],{"__ignoreMap":104},[154,1617,1618,1621,1623,1626,1628,1630,1632,1635,1637,1640,1642],{"class":156,"line":157},[154,1619,1620],{"class":193},"        distanceText ",[154,1622,198],{"class":197},[154,1624,1625],{"class":348}," findViewById",[154,1627,352],{"class":193},[154,1629,1422],{"class":164},[154,1631,298],{"class":174},[154,1633,1634],{"class":164},"id",[154,1636,298],{"class":174},[154,1638,1639],{"class":164},"distance",[154,1641,1076],{"class":193},[154,1643,205],{"class":174},[154,1645,1646,1649,1651,1653,1655,1657,1659,1661,1663,1665,1667],{"class":156,"line":178},[154,1647,1648],{"class":193},"        startText ",[154,1650,198],{"class":197},[154,1652,1625],{"class":348},[154,1654,352],{"class":193},[154,1656,1422],{"class":164},[154,1658,298],{"class":174},[154,1660,1634],{"class":164},[154,1662,298],{"class":174},[154,1664,1605],{"class":164},[154,1666,1076],{"class":193},[154,1668,205],{"class":174},[154,1670,1671,1674,1676,1678,1680,1682,1684,1686,1688,1691,1693],{"class":156,"line":208},[154,1672,1673],{"class":193},"        locationText ",[154,1675,198],{"class":197},[154,1677,1625],{"class":348},[154,1679,352],{"class":193},[154,1681,1422],{"class":164},[154,1683,298],{"class":174},[154,1685,1634],{"class":164},[154,1687,298],{"class":174},[154,1689,1690],{"class":164},"location",[154,1692,1076],{"class":193},[154,1694,205],{"class":174},[154,1696,1697],{"class":156,"line":215},[154,1698,212],{"emptyLinePlaceholder":211},[154,1700,1701,1704,1706,1708,1710,1712,1714,1716,1718,1721,1723],{"class":156,"line":234},[154,1702,1703],{"class":193},"        goButton ",[154,1705,198],{"class":197},[154,1707,1625],{"class":348},[154,1709,352],{"class":193},[154,1711,1422],{"class":164},[154,1713,298],{"class":174},[154,1715,1634],{"class":164},[154,1717,298],{"class":174},[154,1719,1720],{"class":164},"goButton",[154,1722,1076],{"class":193},[154,1724,205],{"class":174},[154,1726,1727,1730,1732,1734,1736,1738,1740,1742,1744,1747,1749],{"class":156,"line":256},[154,1728,1729],{"class":193},"        stopButton ",[154,1731,198],{"class":197},[154,1733,1625],{"class":348},[154,1735,352],{"class":193},[154,1737,1422],{"class":164},[154,1739,298],{"class":174},[154,1741,1634],{"class":164},[154,1743,298],{"class":174},[154,1745,1746],{"class":164},"stopButton",[154,1748,1076],{"class":193},[154,1750,205],{"class":174},[154,1752,1753,1756,1758,1761,1764,1766],{"class":156,"line":374},[154,1754,1755],{"class":164},"        goButton",[154,1757,298],{"class":174},[154,1759,1760],{"class":348},"setOnClickListener",[154,1762,1763],{"class":174},"(v ",[154,1765,1535],{"class":160},[154,1767,175],{"class":174},[154,1769,1770,1773,1775,1778],{"class":156,"line":402},[154,1771,1772],{"class":348},"            setStart",[154,1774,352],{"class":174},[154,1776,1777],{"class":228},"true",[154,1779,362],{"class":174},[154,1781,1782],{"class":156,"line":427},[154,1783,1596],{"class":174},[154,1785,1786,1789,1791,1793,1795,1797],{"class":156,"line":450},[154,1787,1788],{"class":164},"        stopButton",[154,1790,298],{"class":174},[154,1792,1760],{"class":348},[154,1794,1763],{"class":174},[154,1796,1535],{"class":160},[154,1798,175],{"class":174},[154,1800,1801,1803,1805,1808],{"class":156,"line":458},[154,1802,1772],{"class":348},[154,1804,352],{"class":174},[154,1806,1807],{"class":228},"false",[154,1809,362],{"class":174},[154,1811,1812],{"class":156,"line":463},[154,1813,1596],{"class":174},[154,1815,1816,1819,1821,1823,1825,1827,1829,1831,1834],{"class":156,"line":485},[154,1817,1818],{"class":348},"        setStart",[154,1820,352],{"class":193},[154,1822,1549],{"class":348},[154,1824,352],{"class":193},[154,1826,1554],{"class":164},[154,1828,298],{"class":174},[154,1830,161],{"class":164},[154,1832,1833],{"class":193},"))",[154,1835,205],{"class":174},[154,1837,1838,1841,1843,1845,1847,1849],{"class":156,"line":499},[154,1839,1840],{"class":164},"        MainActivity",[154,1842,298],{"class":174},[154,1844,1515],{"class":164},[154,1846,298],{"class":174},[154,1848,1589],{"class":348},[154,1850,610],{"class":174},[154,1852,1853],{"class":156,"line":504},[154,1854,367],{"class":193},[13,1856,1857],{},"Comme le LocationManager est un thread, nous devons faire attention à repasser dans le thread de l'UI afin de mettre\nà jour les labels :",[145,1859,1861],{"className":147,"code":1860,"language":149,"meta":104,"style":104},"    public void updateTextLocation() {\n        runOnUiThread(() -> {\n            if (this.startLocation != null && this.runLocation != null) {\n                distanceText.setText(getString(R.string.distanceLabel, (int) ArroundLocationManager.getDistance(startLocation, runLocation)));\n            } else {\n                distanceText.setText(\"\");\n            }\n            if (this.startLocation != null) {\n                startText.setText(getAddress(startLocation));\n            } else {\n                startText.setText(\"\");\n            }\n            if (this.runLocation != null) {\n                locationText.setText(getAddress(runLocation));\n            } else {\n                locationText.setText(\"\");\n            }\n        });\n    }\n",[151,1862,1863,1874,1886,1920,1968,1978,1993,1997,2015,2032,2040,2054,2058,2076,2092,2100,2114,2118,2124],{"__ignoreMap":104},[154,1864,1865,1867,1869,1872],{"class":156,"line":157},[154,1866,332],{"class":160},[154,1868,468],{"class":160},[154,1870,1871],{"class":348}," updateTextLocation",[154,1873,597],{"class":193},[154,1875,1876,1879,1882,1884],{"class":156,"line":178},[154,1877,1878],{"class":348},"        runOnUiThread",[154,1880,1881],{"class":193},"(() ",[154,1883,1535],{"class":160},[154,1885,175],{"class":193},[154,1887,1888,1890,1892,1894,1896,1898,1901,1903,1906,1909,1911,1914,1916,1918],{"class":156,"line":208},[154,1889,1171],{"class":160},[154,1891,806],{"class":193},[154,1893,1515],{"class":164},[154,1895,298],{"class":174},[154,1897,1293],{"class":164},[154,1899,1900],{"class":197}," !=",[154,1902,229],{"class":228},[154,1904,1905],{"class":197}," &&",[154,1907,1908],{"class":164}," this",[154,1910,298],{"class":174},[154,1912,1913],{"class":164},"runLocation",[154,1915,1900],{"class":197},[154,1917,229],{"class":228},[154,1919,815],{"class":193},[154,1921,1922,1925,1927,1930,1932,1935,1937,1939,1941,1944,1946,1949,1952,1955,1958,1960,1962,1965],{"class":156,"line":215},[154,1923,1924],{"class":164},"                distanceText",[154,1926,298],{"class":174},[154,1928,1929],{"class":348},"setText",[154,1931,352],{"class":174},[154,1933,1934],{"class":348},"getString",[154,1936,352],{"class":174},[154,1938,1422],{"class":164},[154,1940,298],{"class":174},[154,1942,1943],{"class":164},"string",[154,1945,298],{"class":174},[154,1947,1948],{"class":164},"distanceLabel",[154,1950,1951],{"class":174},", (",[154,1953,1954],{"class":160},"int",[154,1956,1957],{"class":174},") ",[154,1959,136],{"class":164},[154,1961,298],{"class":174},[154,1963,1964],{"class":348},"getDistance",[154,1966,1967],{"class":174},"(startLocation, runLocation)));\n",[154,1969,1970,1973,1976],{"class":156,"line":234},[154,1971,1972],{"class":193},"            } ",[154,1974,1975],{"class":160},"else",[154,1977,175],{"class":193},[154,1979,1980,1982,1984,1986,1988,1991],{"class":156,"line":256},[154,1981,1924],{"class":164},[154,1983,298],{"class":174},[154,1985,1929],{"class":348},[154,1987,352],{"class":174},[154,1989,1990],{"class":201},"\"\"",[154,1992,362],{"class":174},[154,1994,1995],{"class":156,"line":374},[154,1996,1196],{"class":193},[154,1998,1999,2001,2003,2005,2007,2009,2011,2013],{"class":156,"line":402},[154,2000,1171],{"class":160},[154,2002,806],{"class":193},[154,2004,1515],{"class":164},[154,2006,298],{"class":174},[154,2008,1293],{"class":164},[154,2010,1900],{"class":197},[154,2012,229],{"class":228},[154,2014,815],{"class":193},[154,2016,2017,2020,2022,2024,2026,2029],{"class":156,"line":427},[154,2018,2019],{"class":164},"                startText",[154,2021,298],{"class":174},[154,2023,1929],{"class":348},[154,2025,352],{"class":174},[154,2027,2028],{"class":348},"getAddress",[154,2030,2031],{"class":174},"(startLocation));\n",[154,2033,2034,2036,2038],{"class":156,"line":450},[154,2035,1972],{"class":193},[154,2037,1975],{"class":160},[154,2039,175],{"class":193},[154,2041,2042,2044,2046,2048,2050,2052],{"class":156,"line":458},[154,2043,2019],{"class":164},[154,2045,298],{"class":174},[154,2047,1929],{"class":348},[154,2049,352],{"class":174},[154,2051,1990],{"class":201},[154,2053,362],{"class":174},[154,2055,2056],{"class":156,"line":463},[154,2057,1196],{"class":193},[154,2059,2060,2062,2064,2066,2068,2070,2072,2074],{"class":156,"line":485},[154,2061,1171],{"class":160},[154,2063,806],{"class":193},[154,2065,1515],{"class":164},[154,2067,298],{"class":174},[154,2069,1913],{"class":164},[154,2071,1900],{"class":197},[154,2073,229],{"class":228},[154,2075,815],{"class":193},[154,2077,2078,2081,2083,2085,2087,2089],{"class":156,"line":499},[154,2079,2080],{"class":164},"                locationText",[154,2082,298],{"class":174},[154,2084,1929],{"class":348},[154,2086,352],{"class":174},[154,2088,2028],{"class":348},[154,2090,2091],{"class":174},"(runLocation));\n",[154,2093,2094,2096,2098],{"class":156,"line":504},[154,2095,1972],{"class":193},[154,2097,1975],{"class":160},[154,2099,175],{"class":193},[154,2101,2102,2104,2106,2108,2110,2112],{"class":156,"line":509},[154,2103,2080],{"class":164},[154,2105,298],{"class":174},[154,2107,1929],{"class":348},[154,2109,352],{"class":174},[154,2111,1990],{"class":201},[154,2113,362],{"class":174},[154,2115,2116],{"class":156,"line":526},[154,2117,1196],{"class":193},[154,2119,2120,2122],{"class":156,"line":549},[154,2121,1489],{"class":193},[154,2123,205],{"class":174},[154,2125,2126],{"class":156,"line":563},[154,2127,367],{"class":193},[13,2129,2130],{},"Pour ma part je ne connais pas la position GPS de ma maison par coeur, ni de là ou je me trouve. Cela tombe bien. Android\npropose une API pour geocoder une adresse. C'est à dire que l'on transforme une position en latitude, longitude en adresse\nlisible:",[145,2132,2134],{"className":147,"code":2133,"language":149,"meta":104,"style":104},"    String getAddress(Location location) {\n        Geocoder geocoder;\n        List\u003CAddress> addresses;\n\n        try {\n            geocoder = new Geocoder(this, Locale.getDefault());\n\n            addresses = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);\n\n            if (addresses.size() > 0 && addresses.get(0).getMaxAddressLineIndex() >= 0) {\n                return addresses.get(0).getAddressLine(0);\n            }\n            return \"Unknown\";\n        } catch (IOException e) {\n            return e.getMessage();\n        }\n    }\n",[151,2135,2136,2150,2160,2177,2181,2187,2219,2223,2259,2263,2313,2339,2343,2353,2369,2381,2385],{"__ignoreMap":104},[154,2137,2138,2141,2144,2146,2148],{"class":156,"line":157},[154,2139,2140],{"class":164},"    String",[154,2142,2143],{"class":348}," getAddress",[154,2145,352],{"class":193},[154,2147,355],{"class":164},[154,2149,523],{"class":193},[154,2151,2152,2155,2158],{"class":156,"line":178},[154,2153,2154],{"class":164},"        Geocoder",[154,2156,2157],{"class":193}," geocoder",[154,2159,205],{"class":174},[154,2161,2162,2165,2167,2170,2172,2175],{"class":156,"line":208},[154,2163,2164],{"class":164},"        List",[154,2166,293],{"class":174},[154,2168,2169],{"class":164},"Address",[154,2171,304],{"class":174},[154,2173,2174],{"class":193}," addresses",[154,2176,205],{"class":174},[154,2178,2179],{"class":156,"line":215},[154,2180,212],{"emptyLinePlaceholder":211},[154,2182,2183,2185],{"class":156,"line":234},[154,2184,756],{"class":160},[154,2186,175],{"class":193},[154,2188,2189,2192,2194,2196,2199,2201,2203,2205,2208,2210,2213,2215,2217],{"class":156,"line":256},[154,2190,2191],{"class":193},"            geocoder ",[154,2193,198],{"class":197},[154,2195,312],{"class":160},[154,2197,2198],{"class":348}," Geocoder",[154,2200,352],{"class":193},[154,2202,1515],{"class":164},[154,2204,1249],{"class":174},[154,2206,2207],{"class":164}," Locale",[154,2209,298],{"class":174},[154,2211,2212],{"class":348},"getDefault",[154,2214,321],{"class":174},[154,2216,1076],{"class":193},[154,2218,205],{"class":174},[154,2220,2221],{"class":156,"line":374},[154,2222,212],{"emptyLinePlaceholder":211},[154,2224,2225,2228,2230,2232,2234,2237,2239,2241,2243,2245,2247,2249,2251,2253,2255,2257],{"class":156,"line":402},[154,2226,2227],{"class":193},"            addresses ",[154,2229,198],{"class":197},[154,2231,2157],{"class":164},[154,2233,298],{"class":174},[154,2235,2236],{"class":348},"getFromLocation",[154,2238,352],{"class":174},[154,2240,1690],{"class":164},[154,2242,298],{"class":174},[154,2244,1298],{"class":348},[154,2246,1301],{"class":174},[154,2248,1690],{"class":164},[154,2250,298],{"class":174},[154,2252,1308],{"class":348},[154,2254,1301],{"class":174},[154,2256,106],{"class":228},[154,2258,362],{"class":174},[154,2260,2261],{"class":156,"line":427},[154,2262,212],{"emptyLinePlaceholder":211},[154,2264,2265,2267,2269,2272,2274,2277,2279,2282,2285,2287,2289,2291,2294,2296,2298,2301,2304,2306,2309,2311],{"class":156,"line":450},[154,2266,1171],{"class":160},[154,2268,806],{"class":193},[154,2270,2271],{"class":164},"addresses",[154,2273,298],{"class":174},[154,2275,2276],{"class":348},"size",[154,2278,321],{"class":174},[154,2280,2281],{"class":197}," >",[154,2283,2284],{"class":228}," 0",[154,2286,1905],{"class":197},[154,2288,2174],{"class":164},[154,2290,298],{"class":174},[154,2292,2293],{"class":348},"get",[154,2295,352],{"class":174},[154,2297,926],{"class":228},[154,2299,2300],{"class":174},").",[154,2302,2303],{"class":348},"getMaxAddressLineIndex",[154,2305,321],{"class":174},[154,2307,2308],{"class":197}," >=",[154,2310,2284],{"class":228},[154,2312,815],{"class":193},[154,2314,2315,2318,2320,2322,2324,2326,2328,2330,2333,2335,2337],{"class":156,"line":458},[154,2316,2317],{"class":160},"                return",[154,2319,2174],{"class":164},[154,2321,298],{"class":174},[154,2323,2293],{"class":348},[154,2325,352],{"class":174},[154,2327,926],{"class":228},[154,2329,2300],{"class":174},[154,2331,2332],{"class":348},"getAddressLine",[154,2334,352],{"class":174},[154,2336,926],{"class":228},[154,2338,362],{"class":174},[154,2340,2341],{"class":156,"line":463},[154,2342,1196],{"class":193},[154,2344,2345,2348,2351],{"class":156,"line":485},[154,2346,2347],{"class":160},"            return",[154,2349,2350],{"class":201}," \"Unknown\"",[154,2352,205],{"class":174},[154,2354,2355,2357,2359,2361,2364,2367],{"class":156,"line":499},[154,2356,800],{"class":193},[154,2358,803],{"class":160},[154,2360,806],{"class":193},[154,2362,2363],{"class":164},"IOException",[154,2365,2366],{"class":358}," e",[154,2368,815],{"class":193},[154,2370,2371,2373,2375,2377,2379],{"class":156,"line":504},[154,2372,2347],{"class":160},[154,2374,2366],{"class":164},[154,2376,298],{"class":174},[154,2378,876],{"class":348},[154,2380,610],{"class":174},[154,2382,2383],{"class":156,"line":509},[154,2384,566],{"class":193},[154,2386,2387],{"class":156,"line":526},[154,2388,367],{"class":193},[13,2390,2391,2392,2394,2395,2397,2398],{},"@Override\npublic void onRequestPermissionsResult(int requestCode, String",[154,2393],{}," permissions, int",[154,2396],{}," grantResults) {\nArrayList",[1943,2399,2400,2401,2403,2404,2406],{}," permissionsToRequest = new ArrayList\u003C>();\nfor (int i = 0; i \u003C grantResults.length; i++) {\npermissionsToRequest.add(permissions",[154,2402,826],{},");\n}\nif (permissionsToRequest.size() > 0) {\nActivityCompat.requestPermissions(this, permissionsToRequest.toArray(new String",[154,2405,926],{},"), REQUEST_PERMISSIONS_REQUEST_CODE);\n}\n}",[13,2408,2409,2410,2412,2413],{},"private void requestPermissionsIfNecessary(String",[154,2411],{}," permissions) {\nArrayList",[1943,2414,2415,2416,2406],{}," permissionsToRequest = new ArrayList\u003C>();\nfor (String permission : permissions) {\nif (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {\n\u002F\u002F Permission is not granted\npermissionsToRequest.add(permission);\n}\n}\nif (permissionsToRequest.size() > 0) {\nActivityCompat.requestPermissions(this, permissionsToRequest.toArray(new String",[154,2417,926],{},[64,2419,2421],{"id":2420},"le-service","Le service",[13,2423,2424,2425,2428],{},"Le service est démarré par l'application et doit ensuite survivre à la fermeture de l'application. Pour cela nous\nallons créer un ",[72,2426,2427],{},"foreground service"," qui, contrairement aux services en tâche de fond qui sont déclenchés sur un évènement avec\nune durée de vie relativement courte, va tourner au premier plan en affichant une notification.",[13,2430,2431,2432,143],{},"Pour démarrer le service depuis l'activité principale nous avons ajouté la méthode ",[151,2433,2434],{},"startServer",[145,2436,2438],{"className":147,"code":2437,"language":149,"meta":104,"style":104},"    private void startServer() {\n        if (!mBounded) {\n            Intent mIntent = new Intent(this, ArroundService.class);\n            mIntent.putExtra(\"startLocation\", startLocation);\n            ContextCompat.startForegroundService(this, mIntent);\n            bindService(mIntent, mConnection, BIND_AUTO_CREATE);\n        }\n    }\n",[151,2439,2440,2451,2463,2495,2513,2530,2550,2554],{"__ignoreMap":104},[154,2441,2442,2444,2446,2449],{"class":156,"line":157},[154,2443,181],{"class":160},[154,2445,468],{"class":160},[154,2447,2448],{"class":348}," startServer",[154,2450,597],{"class":193},[154,2452,2453,2456,2458,2460],{"class":156,"line":178},[154,2454,2455],{"class":160},"        if",[154,2457,806],{"class":193},[154,2459,1546],{"class":197},[154,2461,2462],{"class":193},"mBounded) {\n",[154,2464,2465,2468,2471,2473,2475,2478,2480,2482,2484,2487,2489,2491,2493],{"class":156,"line":208},[154,2466,2467],{"class":164},"            Intent",[154,2469,2470],{"class":193}," mIntent ",[154,2472,198],{"class":197},[154,2474,312],{"class":160},[154,2476,2477],{"class":348}," Intent",[154,2479,352],{"class":193},[154,2481,1515],{"class":164},[154,2483,1249],{"class":174},[154,2485,2486],{"class":164}," ArroundService",[154,2488,298],{"class":174},[154,2490,161],{"class":164},[154,2492,1076],{"class":193},[154,2494,205],{"class":174},[154,2496,2497,2500,2502,2505,2507,2510],{"class":156,"line":215},[154,2498,2499],{"class":164},"            mIntent",[154,2501,298],{"class":174},[154,2503,2504],{"class":348},"putExtra",[154,2506,352],{"class":174},[154,2508,2509],{"class":201},"\"startLocation\"",[154,2511,2512],{"class":174},", startLocation);\n",[154,2514,2515,2518,2520,2523,2525,2527],{"class":156,"line":234},[154,2516,2517],{"class":164},"            ContextCompat",[154,2519,298],{"class":174},[154,2521,2522],{"class":348},"startForegroundService",[154,2524,352],{"class":174},[154,2526,1515],{"class":164},[154,2528,2529],{"class":174},", mIntent);\n",[154,2531,2532,2535,2538,2540,2543,2545,2548],{"class":156,"line":256},[154,2533,2534],{"class":348},"            bindService",[154,2536,2537],{"class":193},"(mIntent",[154,2539,1249],{"class":174},[154,2541,2542],{"class":193}," mConnection",[154,2544,1249],{"class":174},[154,2546,2547],{"class":193}," BIND_AUTO_CREATE)",[154,2549,205],{"class":174},[154,2551,2552],{"class":156,"line":374},[154,2553,566],{"class":193},[154,2555,2556],{"class":156,"line":402},[154,2557,367],{"class":193},[13,2559,2560,2561,2563,2564,2567,2568,2571],{},"Ce qui est important c'est la méthode ",[151,2562,2522],{}," dont le but est de démarrer le service en mode ",[72,2565,2566],{},"Foreground",".\nCette méthode a son pendant dans le service qui est ",[151,2569,2570],{},"startForeground",". Si cette dernière n'est pas appelée dans le service\nune erreur sera remontée par Android.",[13,2573,2574],{},"Du coup on implémente le service et on commence par l'initialisation :",[145,2576,2578],{"className":147,"code":2577,"language":149,"meta":104,"style":104},"    @Override\n    public void onCreate() {\n        super.onCreate();\n        notificationManager = new ArroundNotificationManager(this);\n        locationManager = new ArroundLocationManager(this);\n        locationManager.addListener(location -> {\n            runLocation = location;\n            callListener(location);\n            notificationManager.send(getDistance());\n            speakDistance();\n        });\n        locationManager.start();\n        textToSpeech = new TextToSpeech(this, this);\n    }\n",[151,2579,2580,2586,2596,2606,2626,2644,2659,2668,2674,2690,2697,2701,2711,2735],{"__ignoreMap":104},[154,2581,2582,2584],{"class":156,"line":157},[154,2583,1380],{"class":174},[154,2585,1128],{"class":164},[154,2587,2588,2590,2592,2594],{"class":156,"line":178},[154,2589,332],{"class":160},[154,2591,468],{"class":160},[154,2593,1391],{"class":348},[154,2595,597],{"class":193},[154,2597,2598,2600,2602,2604],{"class":156,"line":208},[154,2599,1404],{"class":164},[154,2601,298],{"class":174},[154,2603,1409],{"class":348},[154,2605,610],{"class":174},[154,2607,2608,2611,2613,2615,2618,2620,2622,2624],{"class":156,"line":215},[154,2609,2610],{"class":193},"        notificationManager ",[154,2612,198],{"class":197},[154,2614,312],{"class":160},[154,2616,2617],{"class":348}," ArroundNotificationManager",[154,2619,352],{"class":193},[154,2621,1515],{"class":164},[154,2623,1076],{"class":193},[154,2625,205],{"class":174},[154,2627,2628,2630,2632,2634,2636,2638,2640,2642],{"class":156,"line":234},[154,2629,1504],{"class":193},[154,2631,198],{"class":197},[154,2633,312],{"class":160},[154,2635,165],{"class":348},[154,2637,352],{"class":193},[154,2639,1515],{"class":164},[154,2641,1076],{"class":193},[154,2643,205],{"class":174},[154,2645,2646,2648,2650,2652,2655,2657],{"class":156,"line":256},[154,2647,1524],{"class":164},[154,2649,298],{"class":174},[154,2651,1529],{"class":348},[154,2653,2654],{"class":174},"(location ",[154,2656,1535],{"class":160},[154,2658,175],{"class":174},[154,2660,2661,2664,2666],{"class":156,"line":374},[154,2662,2663],{"class":174},"            runLocation ",[154,2665,198],{"class":197},[154,2667,1571],{"class":174},[154,2669,2670,2672],{"class":156,"line":402},[154,2671,1205],{"class":348},[154,2673,560],{"class":174},[154,2675,2676,2679,2681,2684,2686,2688],{"class":156,"line":427},[154,2677,2678],{"class":164},"            notificationManager",[154,2680,298],{"class":174},[154,2682,2683],{"class":348},"send",[154,2685,352],{"class":174},[154,2687,1964],{"class":348},[154,2689,879],{"class":174},[154,2691,2692,2695],{"class":156,"line":450},[154,2693,2694],{"class":348},"            speakDistance",[154,2696,610],{"class":174},[154,2698,2699],{"class":156,"line":458},[154,2700,1596],{"class":174},[154,2702,2703,2705,2707,2709],{"class":156,"line":463},[154,2704,1524],{"class":164},[154,2706,298],{"class":174},[154,2708,1605],{"class":348},[154,2710,610],{"class":174},[154,2712,2713,2716,2718,2720,2723,2725,2727,2729,2731,2733],{"class":156,"line":485},[154,2714,2715],{"class":193},"        textToSpeech ",[154,2717,198],{"class":197},[154,2719,312],{"class":160},[154,2721,2722],{"class":348}," TextToSpeech",[154,2724,352],{"class":193},[154,2726,1515],{"class":164},[154,2728,1249],{"class":174},[154,2730,1908],{"class":164},[154,2732,1076],{"class":193},[154,2734,205],{"class":174},[154,2736,2737],{"class":156,"line":499},[154,2738,367],{"class":193},[13,2740,2741],{},"On démarre le notification manager qui a pour but de notifier l'utilisateur de l'existence d'un service qui tourne au\n1er plan. La notification est d'ailleurs nécessaire pour un service foreground.",[13,2743,2744,2745,2747],{},"On écoute aussi notre ",[151,2746,136],{}," qui lors des modifications s'occupe de mettre à jour la nouvelle position\net communique le changement de distance à l'utilisateur par voix et par notification.",[13,2749,2750,2751,2754],{},"On retrouve un ",[151,2752,2753],{},"callListener"," pour avertir l'utilisateur sur l'activité principale quand cette dernière est démarrée.",[13,2756,2757],{},"Ensuite le service démarre:",[145,2759,2761],{"className":147,"code":2760,"language":149,"meta":104,"style":104},"    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        Log.e(TAG, \"onStartCommand\");\n\n        PowerManager powerService = (PowerManager) getSystemService(Context.POWER_SERVICE);\n        wakeLock = powerService.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, \"ArroundService::lock\");\n        wakeLock.acquire();\n\n        Bundle extras = intent.getExtras();\n        Location location = (Location) extras.get(\"startLocation\");\n        if (location != null) {\n            this.startLocation = location;\n        }\n\n        Notification notification = notificationManager.notifyDistance(0);\n        startForeground(ArroundNotificationManager.ARROUND_ID, notification);\n\n        return START_STICKY;\n    }\n",[151,2762,2763,2769,2800,2816,2820,2850,2883,2895,2899,2918,2943,2957,2973,2977,2981,3005,3027,3031,3040],{"__ignoreMap":104},[154,2764,2765,2767],{"class":156,"line":157},[154,2766,1380],{"class":174},[154,2768,1128],{"class":164},[154,2770,2771,2773,2775,2778,2780,2783,2786,2788,2790,2793,2795,2797],{"class":156,"line":178},[154,2772,332],{"class":160},[154,2774,243],{"class":160},[154,2776,2777],{"class":348}," onStartCommand",[154,2779,352],{"class":193},[154,2781,2782],{"class":164},"Intent",[154,2784,2785],{"class":193}," intent",[154,2787,1249],{"class":174},[154,2789,243],{"class":160},[154,2791,2792],{"class":193}," flags",[154,2794,1249],{"class":174},[154,2796,243],{"class":160},[154,2798,2799],{"class":193}," startId) {\n",[154,2801,2802,2805,2807,2809,2811,2814],{"class":156,"line":208},[154,2803,2804],{"class":164},"        Log",[154,2806,298],{"class":174},[154,2808,1087],{"class":348},[154,2810,829],{"class":174},[154,2812,2813],{"class":201},"\"onStartCommand\"",[154,2815,362],{"class":174},[154,2817,2818],{"class":156,"line":215},[154,2819,212],{"emptyLinePlaceholder":211},[154,2821,2822,2825,2828,2830,2833,2836,2838,2841,2843,2846,2848],{"class":156,"line":234},[154,2823,2824],{"class":164},"        PowerManager",[154,2826,2827],{"class":193}," powerService ",[154,2829,198],{"class":197},[154,2831,2832],{"class":193}," (PowerManager) ",[154,2834,2835],{"class":348},"getSystemService",[154,2837,352],{"class":193},[154,2839,2840],{"class":164},"Context",[154,2842,298],{"class":174},[154,2844,2845],{"class":164},"POWER_SERVICE",[154,2847,1076],{"class":193},[154,2849,205],{"class":174},[154,2851,2852,2855,2857,2860,2862,2865,2867,2870,2872,2875,2878,2881],{"class":156,"line":256},[154,2853,2854],{"class":193},"        wakeLock ",[154,2856,198],{"class":197},[154,2858,2859],{"class":164}," powerService",[154,2861,298],{"class":174},[154,2863,2864],{"class":348},"newWakeLock",[154,2866,352],{"class":174},[154,2868,2869],{"class":164},"PowerManager",[154,2871,298],{"class":174},[154,2873,2874],{"class":164},"PARTIAL_WAKE_LOCK",[154,2876,2877],{"class":174},", ",[154,2879,2880],{"class":201},"\"ArroundService::lock\"",[154,2882,362],{"class":174},[154,2884,2885,2888,2890,2893],{"class":156,"line":374},[154,2886,2887],{"class":164},"        wakeLock",[154,2889,298],{"class":174},[154,2891,2892],{"class":348},"acquire",[154,2894,610],{"class":174},[154,2896,2897],{"class":156,"line":402},[154,2898,212],{"emptyLinePlaceholder":211},[154,2900,2901,2904,2907,2909,2911,2913,2916],{"class":156,"line":427},[154,2902,2903],{"class":164},"        Bundle",[154,2905,2906],{"class":193}," extras ",[154,2908,198],{"class":197},[154,2910,2785],{"class":164},[154,2912,298],{"class":174},[154,2914,2915],{"class":348},"getExtras",[154,2917,610],{"class":174},[154,2919,2920,2922,2925,2927,2930,2933,2935,2937,2939,2941],{"class":156,"line":450},[154,2921,1283],{"class":164},[154,2923,2924],{"class":193}," location ",[154,2926,198],{"class":197},[154,2928,2929],{"class":193}," (Location) ",[154,2931,2932],{"class":164},"extras",[154,2934,298],{"class":174},[154,2936,2293],{"class":348},[154,2938,352],{"class":174},[154,2940,2509],{"class":201},[154,2942,362],{"class":174},[154,2944,2945,2947,2950,2953,2955],{"class":156,"line":458},[154,2946,2455],{"class":160},[154,2948,2949],{"class":193}," (location ",[154,2951,2952],{"class":197},"!=",[154,2954,229],{"class":228},[154,2956,815],{"class":193},[154,2958,2959,2962,2964,2966,2969,2971],{"class":156,"line":463},[154,2960,2961],{"class":164},"            this",[154,2963,298],{"class":174},[154,2965,1293],{"class":164},[154,2967,2968],{"class":197}," =",[154,2970,1144],{"class":193},[154,2972,205],{"class":174},[154,2974,2975],{"class":156,"line":485},[154,2976,566],{"class":193},[154,2978,2979],{"class":156,"line":499},[154,2980,212],{"emptyLinePlaceholder":211},[154,2982,2983,2986,2989,2991,2994,2996,2999,3001,3003],{"class":156,"line":504},[154,2984,2985],{"class":164},"        Notification",[154,2987,2988],{"class":193}," notification ",[154,2990,198],{"class":197},[154,2992,2993],{"class":164}," notificationManager",[154,2995,298],{"class":174},[154,2997,2998],{"class":348},"notifyDistance",[154,3000,352],{"class":174},[154,3002,926],{"class":228},[154,3004,362],{"class":174},[154,3006,3007,3010,3012,3015,3017,3020,3022,3025],{"class":156,"line":509},[154,3008,3009],{"class":348},"        startForeground",[154,3011,352],{"class":193},[154,3013,3014],{"class":164},"ArroundNotificationManager",[154,3016,298],{"class":174},[154,3018,3019],{"class":164},"ARROUND_ID",[154,3021,1249],{"class":174},[154,3023,3024],{"class":193}," notification)",[154,3026,205],{"class":174},[154,3028,3029],{"class":156,"line":526},[154,3030,212],{"emptyLinePlaceholder":211},[154,3032,3033,3035,3038],{"class":156,"line":549},[154,3034,1351],{"class":160},[154,3036,3037],{"class":193}," START_STICKY",[154,3039,205],{"class":174},[154,3041,3042],{"class":156,"line":563},[154,3043,367],{"class":193},[13,3045,3046],{},"Afin qu'Android et son système de gestion d'nergie ne tuent pas notre service pendant que l'on court, nous commençons\npar mettre un PARTIAL_WAKE_LOCK. Ensuite nous récupérons la position (sauf si nous sommes issus d'un redémarrage de\nl'application) et appelons la méthode startForeground avec une notification persistante que nous avons créé.",[13,3048,3049],{},"Surtout après l'acquision du Wake Lock, il est important de le relâcher lors de la fermeture (quand l'utilisateur\nclique sur stop):",[145,3051,3053],{"className":147,"code":3052,"language":149,"meta":104,"style":104},"    public void stop() {\n        if (wakeLock != null) {\n            if (wakeLock.isHeld()) {\n                wakeLock.release();\n                wakeLock = null;\n            }\n        }\n        stopForeground(true);\n        stopSelf();\n    }\n",[151,3054,3055,3066,3079,3097,3109,3120,3124,3128,3141,3150],{"__ignoreMap":104},[154,3056,3057,3059,3061,3064],{"class":156,"line":157},[154,3058,332],{"class":160},[154,3060,468],{"class":160},[154,3062,3063],{"class":348}," stop",[154,3065,597],{"class":193},[154,3067,3068,3070,3073,3075,3077],{"class":156,"line":178},[154,3069,2455],{"class":160},[154,3071,3072],{"class":193}," (wakeLock ",[154,3074,2952],{"class":197},[154,3076,229],{"class":228},[154,3078,815],{"class":193},[154,3080,3081,3083,3085,3088,3090,3093,3095],{"class":156,"line":208},[154,3082,1171],{"class":160},[154,3084,806],{"class":193},[154,3086,3087],{"class":164},"wakeLock",[154,3089,298],{"class":174},[154,3091,3092],{"class":348},"isHeld",[154,3094,321],{"class":174},[154,3096,815],{"class":193},[154,3098,3099,3102,3104,3107],{"class":156,"line":215},[154,3100,3101],{"class":164},"                wakeLock",[154,3103,298],{"class":174},[154,3105,3106],{"class":348},"release",[154,3108,610],{"class":174},[154,3110,3111,3114,3116,3118],{"class":156,"line":234},[154,3112,3113],{"class":193},"                wakeLock ",[154,3115,198],{"class":197},[154,3117,229],{"class":228},[154,3119,205],{"class":174},[154,3121,3122],{"class":156,"line":256},[154,3123,1196],{"class":193},[154,3125,3126],{"class":156,"line":374},[154,3127,566],{"class":193},[154,3129,3130,3133,3135,3137,3139],{"class":156,"line":402},[154,3131,3132],{"class":348},"        stopForeground",[154,3134,352],{"class":193},[154,3136,1777],{"class":228},[154,3138,1076],{"class":193},[154,3140,205],{"class":174},[154,3142,3143,3146,3148],{"class":156,"line":427},[154,3144,3145],{"class":348},"        stopSelf",[154,3147,321],{"class":193},[154,3149,205],{"class":174},[154,3151,3152],{"class":156,"line":450},[154,3153,367],{"class":193},[13,3155,3156,3157,3160,3161,2300],{},"On en profite pour arrêter la notification (avec ̀",[151,3158,3159],{},"stopForeground",") et arrêter le service (avec ",[151,3162,3163],{},"stopSelf",[13,3165,3166,3167,3170,3171,143],{},"Pour lire à l'utilisateur la distance, nous utilisons ",[151,3168,3169],{},"android.speech.tts.TextToSpeech",". Son utilisation\nest fort simple et se fait lors de l'appel à ",[151,3172,3173],{},"speakDistance",[145,3175,3177],{"className":147,"code":3176,"language":149,"meta":104,"style":104},"    private void speakDistance() {\n        int distance = (int) getDistance();\n        if (Math.abs(lastDistance - distance) > 100) {\n            int stringId;\n            if (distance > 1000) {\n                stringId = R.string.speaker_meters_alert;\n            } else if (distance > 900) {\n                stringId = R.string.speaker_meters_warn;\n            } else {\n                stringId = R.string.speaker_meters_info;\n            }\n\n            String text = getString(stringId, distance);\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, null);\n            } else {\n                textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null);\n            }\n\n            lastDistance = distance;\n        }\n    }\n",[151,3178,3179,3190,3211,3241,3251,3264,3285,3303,3322,3330,3349,3353,3357,3379,3383,3419,3451,3459,3481,3485,3489,3500,3504],{"__ignoreMap":104},[154,3180,3181,3183,3185,3188],{"class":156,"line":157},[154,3182,181],{"class":160},[154,3184,468],{"class":160},[154,3186,3187],{"class":348}," speakDistance",[154,3189,597],{"class":193},[154,3191,3192,3195,3197,3199,3201,3203,3205,3207,3209],{"class":156,"line":178},[154,3193,3194],{"class":160},"        int",[154,3196,1335],{"class":193},[154,3198,198],{"class":197},[154,3200,806],{"class":193},[154,3202,1954],{"class":160},[154,3204,1957],{"class":193},[154,3206,1964],{"class":348},[154,3208,321],{"class":193},[154,3210,205],{"class":174},[154,3212,3213,3215,3217,3220,3222,3225,3228,3231,3234,3236,3239],{"class":156,"line":208},[154,3214,2455],{"class":160},[154,3216,806],{"class":193},[154,3218,3219],{"class":164},"Math",[154,3221,298],{"class":174},[154,3223,3224],{"class":348},"abs",[154,3226,3227],{"class":174},"(lastDistance ",[154,3229,3230],{"class":197},"-",[154,3232,3233],{"class":174}," distance)",[154,3235,2281],{"class":197},[154,3237,3238],{"class":228}," 100",[154,3240,815],{"class":193},[154,3242,3243,3246,3249],{"class":156,"line":215},[154,3244,3245],{"class":160},"            int",[154,3247,3248],{"class":193}," stringId",[154,3250,205],{"class":174},[154,3252,3253,3255,3258,3260,3262],{"class":156,"line":234},[154,3254,1171],{"class":160},[154,3256,3257],{"class":193}," (distance ",[154,3259,304],{"class":197},[154,3261,251],{"class":228},[154,3263,815],{"class":193},[154,3265,3266,3269,3271,3274,3276,3278,3280,3283],{"class":156,"line":256},[154,3267,3268],{"class":193},"                stringId ",[154,3270,198],{"class":197},[154,3272,3273],{"class":164}," R",[154,3275,298],{"class":174},[154,3277,1943],{"class":164},[154,3279,298],{"class":174},[154,3281,3282],{"class":164},"speaker_meters_alert",[154,3284,205],{"class":174},[154,3286,3287,3289,3291,3294,3296,3298,3301],{"class":156,"line":374},[154,3288,1972],{"class":193},[154,3290,1975],{"class":160},[154,3292,3293],{"class":160}," if",[154,3295,3257],{"class":193},[154,3297,304],{"class":197},[154,3299,3300],{"class":228}," 900",[154,3302,815],{"class":193},[154,3304,3305,3307,3309,3311,3313,3315,3317,3320],{"class":156,"line":402},[154,3306,3268],{"class":193},[154,3308,198],{"class":197},[154,3310,3273],{"class":164},[154,3312,298],{"class":174},[154,3314,1943],{"class":164},[154,3316,298],{"class":174},[154,3318,3319],{"class":164},"speaker_meters_warn",[154,3321,205],{"class":174},[154,3323,3324,3326,3328],{"class":156,"line":427},[154,3325,1972],{"class":193},[154,3327,1975],{"class":160},[154,3329,175],{"class":193},[154,3331,3332,3334,3336,3338,3340,3342,3344,3347],{"class":156,"line":450},[154,3333,3268],{"class":193},[154,3335,198],{"class":197},[154,3337,3273],{"class":164},[154,3339,298],{"class":174},[154,3341,1943],{"class":164},[154,3343,298],{"class":174},[154,3345,3346],{"class":164},"speaker_meters_info",[154,3348,205],{"class":174},[154,3350,3351],{"class":156,"line":458},[154,3352,1196],{"class":193},[154,3354,3355],{"class":156,"line":463},[154,3356,212],{"emptyLinePlaceholder":211},[154,3358,3359,3362,3365,3367,3370,3373,3375,3377],{"class":156,"line":485},[154,3360,3361],{"class":164},"            String",[154,3363,3364],{"class":193}," text ",[154,3366,198],{"class":197},[154,3368,3369],{"class":348}," getString",[154,3371,3372],{"class":193},"(stringId",[154,3374,1249],{"class":174},[154,3376,3233],{"class":193},[154,3378,205],{"class":174},[154,3380,3381],{"class":156,"line":499},[154,3382,212],{"emptyLinePlaceholder":211},[154,3384,3385,3387,3389,3392,3394,3397,3399,3402,3404,3407,3409,3412,3414,3417],{"class":156,"line":504},[154,3386,1171],{"class":160},[154,3388,806],{"class":193},[154,3390,3391],{"class":164},"Build",[154,3393,298],{"class":174},[154,3395,3396],{"class":164},"VERSION",[154,3398,298],{"class":174},[154,3400,3401],{"class":164},"SDK_INT",[154,3403,2308],{"class":197},[154,3405,3406],{"class":164}," Build",[154,3408,298],{"class":174},[154,3410,3411],{"class":164},"VERSION_CODES",[154,3413,298],{"class":174},[154,3415,3416],{"class":164},"LOLLIPOP",[154,3418,815],{"class":193},[154,3420,3421,3424,3426,3429,3432,3435,3437,3440,3442,3445,3447,3449],{"class":156,"line":509},[154,3422,3423],{"class":164},"                textToSpeech",[154,3425,298],{"class":174},[154,3427,3428],{"class":348},"speak",[154,3430,3431],{"class":174},"(text, ",[154,3433,3434],{"class":164},"TextToSpeech",[154,3436,298],{"class":174},[154,3438,3439],{"class":164},"QUEUE_FLUSH",[154,3441,2877],{"class":174},[154,3443,3444],{"class":228},"null",[154,3446,2877],{"class":174},[154,3448,3444],{"class":228},[154,3450,362],{"class":174},[154,3452,3453,3455,3457],{"class":156,"line":526},[154,3454,1972],{"class":193},[154,3456,1975],{"class":160},[154,3458,175],{"class":193},[154,3460,3461,3463,3465,3467,3469,3471,3473,3475,3477,3479],{"class":156,"line":549},[154,3462,3423],{"class":164},[154,3464,298],{"class":174},[154,3466,3428],{"class":348},[154,3468,3431],{"class":174},[154,3470,3434],{"class":164},[154,3472,298],{"class":174},[154,3474,3439],{"class":164},[154,3476,2877],{"class":174},[154,3478,3444],{"class":228},[154,3480,362],{"class":174},[154,3482,3483],{"class":156,"line":563},[154,3484,1196],{"class":193},[154,3486,3487],{"class":156,"line":569},[154,3488,212],{"emptyLinePlaceholder":211},[154,3490,3491,3494,3496,3498],{"class":156,"line":786},[154,3492,3493],{"class":193},"            lastDistance ",[154,3495,198],{"class":197},[154,3497,1354],{"class":193},[154,3499,205],{"class":174},[154,3501,3502],{"class":156,"line":797},[154,3503,566],{"class":193},[154,3505,3506],{"class":156,"line":818},[154,3507,367],{"class":193},[13,3509,3510],{},"Enfin le dernier point concerne la création des notifications. Depuis la version Android O, l'application doit\nassocier une notification à un channel. Cela permet à Android de présenter à l'utilisateur une interface avec les\nnotifications possibles et de pouvoir désactiver\u002Factiver ces dernières au cas par cas.",[13,3512,3513,3516],{},[151,3514,3515],{},"AndroidNotificationManager"," est là pour ce but. Il va créer le channel de notification et notifier la distance à\nl'utilisateur.",[145,3518,3520],{"className":147,"code":3519,"language":149,"meta":104,"style":104},"    private static final String CHANNEL_DEFAULT_IMPORTANCE = \"Running\";\n    public static final int ARROUND_ID = 1;\n\n    private void createNotificationChannel() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            CharSequence name = context.getString(R.string.channel_name);\n            String description = context.getString(R.string.channel_description);\n            NotificationChannel channel = new NotificationChannel(CHANNEL_DEFAULT_IMPORTANCE, name, NotificationManager.IMPORTANCE_DEFAULT);\n            channel.setDescription(description);\n            NotificationManager notificationManager = context.getSystemService(NotificationManager.class);\n            notificationManager.createNotificationChannel(channel);\n        }\n    }\n\n    public Notification createNotification(float distance) {\n        Intent notificationIntent = new Intent(context, MainActivity.class);\n        PendingIntent pendingIntent =\n                PendingIntent.getActivity(context, 0, notificationIntent, 0);\n\n        return new NotificationCompat.Builder(context, CHANNEL_DEFAULT_IMPORTANCE)\n                .setContentTitle(context.getText(R.string.notification_title))\n                .setContentText(context.getString(R.string.notification_message, (int) distance))\n                .setSmallIcon(android.R.drawable.ic_menu_mylocation)\n                .setContentIntent(pendingIntent)\n                .build();\n    }\n\n    public void notifyDistance(float distance) {\n        Notification n = this.createNotification(distance);\n        NotificationManager notificationManager = context.getSystemService(NotificationManager.class);\n        notificationManager.notify(ARROUND_ID, n);\n    }\n",[151,3521,3522,3542,3562,3566,3577,3608,3640,3670,3707,3720,3747,3759,3763,3767,3771,3789,3819,3830,3852,3856,3873,3907,3942,3970,3980,3989,3993,3997,4012,4031,4056,4069],{"__ignoreMap":104},[154,3523,3524,3526,3528,3530,3532,3535,3537,3540],{"class":156,"line":157},[154,3525,181],{"class":160},[154,3527,184],{"class":160},[154,3529,187],{"class":160},[154,3531,190],{"class":164},[154,3533,3534],{"class":193}," CHANNEL_DEFAULT_IMPORTANCE ",[154,3536,198],{"class":197},[154,3538,3539],{"class":201}," \"Running\"",[154,3541,205],{"class":174},[154,3543,3544,3546,3548,3550,3552,3555,3557,3560],{"class":156,"line":178},[154,3545,332],{"class":160},[154,3547,184],{"class":160},[154,3549,187],{"class":160},[154,3551,243],{"class":160},[154,3553,3554],{"class":193}," ARROUND_ID ",[154,3556,198],{"class":197},[154,3558,3559],{"class":228}," 1",[154,3561,205],{"class":174},[154,3563,3564],{"class":156,"line":208},[154,3565,212],{"emptyLinePlaceholder":211},[154,3567,3568,3570,3572,3575],{"class":156,"line":215},[154,3569,181],{"class":160},[154,3571,468],{"class":160},[154,3573,3574],{"class":348}," createNotificationChannel",[154,3576,597],{"class":193},[154,3578,3579,3581,3583,3585,3587,3589,3591,3593,3595,3597,3599,3601,3603,3606],{"class":156,"line":234},[154,3580,2455],{"class":160},[154,3582,806],{"class":193},[154,3584,3391],{"class":164},[154,3586,298],{"class":174},[154,3588,3396],{"class":164},[154,3590,298],{"class":174},[154,3592,3401],{"class":164},[154,3594,2308],{"class":197},[154,3596,3406],{"class":164},[154,3598,298],{"class":174},[154,3600,3411],{"class":164},[154,3602,298],{"class":174},[154,3604,3605],{"class":164},"O",[154,3607,815],{"class":193},[154,3609,3610,3613,3616,3618,3621,3623,3625,3627,3629,3631,3633,3635,3638],{"class":156,"line":256},[154,3611,3612],{"class":164},"            CharSequence",[154,3614,3615],{"class":193}," name ",[154,3617,198],{"class":197},[154,3619,3620],{"class":164}," context",[154,3622,298],{"class":174},[154,3624,1934],{"class":348},[154,3626,352],{"class":174},[154,3628,1422],{"class":164},[154,3630,298],{"class":174},[154,3632,1943],{"class":164},[154,3634,298],{"class":174},[154,3636,3637],{"class":164},"channel_name",[154,3639,362],{"class":174},[154,3641,3642,3644,3647,3649,3651,3653,3655,3657,3659,3661,3663,3665,3668],{"class":156,"line":374},[154,3643,3361],{"class":164},[154,3645,3646],{"class":193}," description ",[154,3648,198],{"class":197},[154,3650,3620],{"class":164},[154,3652,298],{"class":174},[154,3654,1934],{"class":348},[154,3656,352],{"class":174},[154,3658,1422],{"class":164},[154,3660,298],{"class":174},[154,3662,1943],{"class":164},[154,3664,298],{"class":174},[154,3666,3667],{"class":164},"channel_description",[154,3669,362],{"class":174},[154,3671,3672,3675,3678,3680,3682,3685,3688,3690,3693,3695,3698,3700,3703,3705],{"class":156,"line":402},[154,3673,3674],{"class":164},"            NotificationChannel",[154,3676,3677],{"class":193}," channel ",[154,3679,198],{"class":197},[154,3681,312],{"class":160},[154,3683,3684],{"class":348}," NotificationChannel",[154,3686,3687],{"class":193},"(CHANNEL_DEFAULT_IMPORTANCE",[154,3689,1249],{"class":174},[154,3691,3692],{"class":193}," name",[154,3694,1249],{"class":174},[154,3696,3697],{"class":164}," NotificationManager",[154,3699,298],{"class":174},[154,3701,3702],{"class":164},"IMPORTANCE_DEFAULT",[154,3704,1076],{"class":193},[154,3706,205],{"class":174},[154,3708,3709,3712,3714,3717],{"class":156,"line":427},[154,3710,3711],{"class":164},"            channel",[154,3713,298],{"class":174},[154,3715,3716],{"class":348},"setDescription",[154,3718,3719],{"class":174},"(description);\n",[154,3721,3722,3725,3728,3730,3732,3734,3736,3738,3741,3743,3745],{"class":156,"line":450},[154,3723,3724],{"class":164},"            NotificationManager",[154,3726,3727],{"class":193}," notificationManager ",[154,3729,198],{"class":197},[154,3731,3620],{"class":164},[154,3733,298],{"class":174},[154,3735,2835],{"class":348},[154,3737,352],{"class":174},[154,3739,3740],{"class":164},"NotificationManager",[154,3742,298],{"class":174},[154,3744,161],{"class":164},[154,3746,362],{"class":174},[154,3748,3749,3751,3753,3756],{"class":156,"line":458},[154,3750,2678],{"class":164},[154,3752,298],{"class":174},[154,3754,3755],{"class":348},"createNotificationChannel",[154,3757,3758],{"class":174},"(channel);\n",[154,3760,3761],{"class":156,"line":463},[154,3762,566],{"class":193},[154,3764,3765],{"class":156,"line":485},[154,3766,367],{"class":193},[154,3768,3769],{"class":156,"line":499},[154,3770,212],{"emptyLinePlaceholder":211},[154,3772,3773,3775,3778,3781,3783,3786],{"class":156,"line":504},[154,3774,332],{"class":160},[154,3776,3777],{"class":164}," Notification",[154,3779,3780],{"class":348}," createNotification",[154,3782,352],{"class":193},[154,3784,3785],{"class":160},"float",[154,3787,3788],{"class":193}," distance) {\n",[154,3790,3791,3794,3797,3799,3801,3803,3806,3808,3811,3813,3815,3817],{"class":156,"line":509},[154,3792,3793],{"class":164},"        Intent",[154,3795,3796],{"class":193}," notificationIntent ",[154,3798,198],{"class":197},[154,3800,312],{"class":160},[154,3802,2477],{"class":348},[154,3804,3805],{"class":193},"(context",[154,3807,1249],{"class":174},[154,3809,3810],{"class":164}," MainActivity",[154,3812,298],{"class":174},[154,3814,161],{"class":164},[154,3816,1076],{"class":193},[154,3818,205],{"class":174},[154,3820,3821,3824,3827],{"class":156,"line":526},[154,3822,3823],{"class":164},"        PendingIntent",[154,3825,3826],{"class":193}," pendingIntent ",[154,3828,3829],{"class":197},"=\n",[154,3831,3832,3835,3837,3840,3843,3845,3848,3850],{"class":156,"line":549},[154,3833,3834],{"class":164},"                PendingIntent",[154,3836,298],{"class":174},[154,3838,3839],{"class":348},"getActivity",[154,3841,3842],{"class":174},"(context, ",[154,3844,926],{"class":228},[154,3846,3847],{"class":174},", notificationIntent, ",[154,3849,926],{"class":228},[154,3851,362],{"class":174},[154,3853,3854],{"class":156,"line":563},[154,3855,212],{"emptyLinePlaceholder":211},[154,3857,3858,3860,3862,3865,3867,3870],{"class":156,"line":569},[154,3859,1351],{"class":160},[154,3861,312],{"class":160},[154,3863,3864],{"class":193}," NotificationCompat",[154,3866,298],{"class":174},[154,3868,3869],{"class":348},"Builder",[154,3871,3872],{"class":174},"(context, CHANNEL_DEFAULT_IMPORTANCE)\n",[154,3874,3875,3878,3881,3883,3886,3888,3891,3893,3895,3897,3899,3901,3904],{"class":156,"line":786},[154,3876,3877],{"class":174},"                .",[154,3879,3880],{"class":348},"setContentTitle",[154,3882,352],{"class":174},[154,3884,3885],{"class":164},"context",[154,3887,298],{"class":174},[154,3889,3890],{"class":348},"getText",[154,3892,352],{"class":174},[154,3894,1422],{"class":164},[154,3896,298],{"class":174},[154,3898,1943],{"class":164},[154,3900,298],{"class":174},[154,3902,3903],{"class":164},"notification_title",[154,3905,3906],{"class":174},"))\n",[154,3908,3909,3911,3914,3916,3918,3920,3922,3924,3926,3928,3930,3932,3935,3937,3939],{"class":156,"line":797},[154,3910,3877],{"class":174},[154,3912,3913],{"class":348},"setContentText",[154,3915,352],{"class":174},[154,3917,3885],{"class":164},[154,3919,298],{"class":174},[154,3921,1934],{"class":348},[154,3923,352],{"class":174},[154,3925,1422],{"class":164},[154,3927,298],{"class":174},[154,3929,1943],{"class":164},[154,3931,298],{"class":174},[154,3933,3934],{"class":164},"notification_message",[154,3936,1951],{"class":174},[154,3938,1954],{"class":160},[154,3940,3941],{"class":174},") distance))\n",[154,3943,3944,3946,3949,3951,3954,3956,3958,3960,3963,3965,3968],{"class":156,"line":818},[154,3945,3877],{"class":174},[154,3947,3948],{"class":348},"setSmallIcon",[154,3950,352],{"class":174},[154,3952,3953],{"class":164},"android",[154,3955,298],{"class":174},[154,3957,1422],{"class":164},[154,3959,298],{"class":174},[154,3961,3962],{"class":164},"drawable",[154,3964,298],{"class":174},[154,3966,3967],{"class":164},"ic_menu_mylocation",[154,3969,447],{"class":174},[154,3971,3972,3974,3977],{"class":156,"line":838},[154,3973,3877],{"class":174},[154,3975,3976],{"class":348},"setContentIntent",[154,3978,3979],{"class":174},"(pendingIntent)\n",[154,3981,3982,3984,3987],{"class":156,"line":854},[154,3983,3877],{"class":174},[154,3985,3986],{"class":348},"build",[154,3988,610],{"class":174},[154,3990,3991],{"class":156,"line":882},[154,3992,367],{"class":193},[154,3994,3995],{"class":156,"line":887},[154,3996,212],{"emptyLinePlaceholder":211},[154,3998,3999,4001,4003,4006,4008,4010],{"class":156,"line":892},[154,4000,332],{"class":160},[154,4002,468],{"class":160},[154,4004,4005],{"class":348}," notifyDistance",[154,4007,352],{"class":193},[154,4009,3785],{"class":160},[154,4011,3788],{"class":193},[154,4013,4014,4016,4019,4021,4023,4025,4028],{"class":156,"line":899},[154,4015,2985],{"class":164},[154,4017,4018],{"class":193}," n ",[154,4020,198],{"class":197},[154,4022,1908],{"class":164},[154,4024,298],{"class":174},[154,4026,4027],{"class":348},"createNotification",[154,4029,4030],{"class":174},"(distance);\n",[154,4032,4033,4036,4038,4040,4042,4044,4046,4048,4050,4052,4054],{"class":156,"line":910},[154,4034,4035],{"class":164},"        NotificationManager",[154,4037,3727],{"class":193},[154,4039,198],{"class":197},[154,4041,3620],{"class":164},[154,4043,298],{"class":174},[154,4045,2835],{"class":348},[154,4047,352],{"class":174},[154,4049,3740],{"class":164},[154,4051,298],{"class":174},[154,4053,161],{"class":164},[154,4055,362],{"class":174},[154,4057,4058,4061,4063,4066],{"class":156,"line":921},[154,4059,4060],{"class":164},"        notificationManager",[154,4062,298],{"class":174},[154,4064,4065],{"class":348},"notify",[154,4067,4068],{"class":174},"(ARROUND_ID, n);\n",[154,4070,4071],{"class":156,"line":931},[154,4072,367],{"class":193},[13,4074,4075],{},"Il est important lors de l'appel à notifyDistance de toujours utiliser le même identifiant de notification afin\nque cette dernière soit remplacée (et non ajouté). Cela permet de mettre à jour le contenu de la notification.",[64,4077,4079],{"id":4078},"pour-finir","Pour finir",[13,4081,4082],{},"Pour finir je suis content d'avoir développé cette application qui n'est pas exempte de bug, mais qui m'a pris\ntrès peu de temps de développement.",[13,4084,4085,4086,4091,4092,4097],{},"Vous pouvez retrouver le code source sur ",[23,4087,4090],{"href":4088,"rel":4089},"https:\u002F\u002Fgithub.com\u002Fphoenix741\u002F1kmarround",[27],"Github: phoenix741\u002F1kmarround","\net sur le ",[23,4093,4096],{"href":4094,"rel":4095},"https:\u002F\u002Fplay.google.com\u002Fstore\u002Fapps\u002Fdetails?id=org.shadoware.a1kmarroud",[27],"PlayStore"," (à ce jour l'application\nn'a pas encore été validé dans les stores et n'est donc pas disponible).",[13,4099,4100],{},"Le plus long dans ce développement aura été:",[48,4102,4103,4111,4114],{},[51,4104,4105,4106,1076],{},"faire l'icône (à partir d'une image se trouvant sur ",[23,4107,4110],{"href":4108,"rel":4109},"https:\u002F\u002Fundraw.co\u002F",[27],"undraw.co",[51,4112,4113],{},"faire le layout (même si elle est simple)",[51,4115,4116],{},"remplir la fiche du playstore pour mettre l'application dans les stores.",[4118,4119,4122,4127],"section",{"className":4120,"dataFootnotes":104},[4121],"footnotes",[64,4123,4126],{"className":4124,"id":103},[4125],"sr-only","Footnotes",[4128,4129,4130,4141],"ol",{},[51,4131,4133,4134],{"id":4132},"user-content-fn-1","Une activité est l'équivalent d'un écran dans Android. ",[23,4135,4140],{"href":4136,"ariaLabel":4137,"className":4138,"dataFootnoteBackref":104},"#user-content-fnref-1","Back to reference 1",[4139],"data-footnote-backref","↩",[51,4142,4144,4145],{"id":4143},"user-content-fn-2","Pour demander les permissions je me suis basé sur le code suivant: ",[23,4146,4140],{"href":4147,"ariaLabel":4148,"className":4149,"dataFootnoteBackref":104},"#user-content-fnref-2","Back to reference 2",[4139],[4151,4152,4153],"style",{},"html pre.shiki code .seHd6, html code.shiki .seHd6{--shiki-default:#C678DD}html pre.shiki code .sU0A5, html code.shiki .sU0A5{--shiki-default:#E5C07B}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .s_ZVi, html code.shiki .s_ZVi{--shiki-default:#E06C75;--shiki-default-font-style:italic}html pre.shiki code .sV9Aq, html code.shiki .sV9Aq{--shiki-default:#7F848E;--shiki-default-font-style:italic}",{"title":104,"searchDepth":178,"depth":178,"links":4155},[4156,4157,4161,4162,4163],{"id":66,"depth":178,"text":67},{"id":116,"depth":178,"text":95,"children":4158},[4159,4160],{"id":135,"depth":208,"text":136},{"id":1363,"depth":208,"text":1364},{"id":2420,"depth":178,"text":2421},{"id":4078,"depth":178,"text":4079},{"id":103,"depth":178,"text":4126},"Programmation","programmation","2020-11-14",{"type":10,"value":4168},[4169,4171,4173,4178,4183,4185,4187,4189],[13,4170,15],{},[13,4172,18],{},[13,4174,21,4175,29],{},[23,4176,28],{"href":25,"rel":4177},[27],[13,4179,32,4180,37],{},[23,4181,28],{"href":35,"rel":4182},[27],[13,4184,40],{},[13,4186,43],{},[13,4188,46],{},[48,4190,4191,4193,4195,4197],{},[51,4192,53],{},[51,4194,56],{},[51,4196,59],{},[51,4198,62],{},"md","Lille, France",{"planet":211},"\u002Fpost\u002Fapplication_du_confinement",{"title":7,"description":15},"application_du_confinement","posts\u002FProgrammation\u002F2020-11-14_application_du_confinement",[3953,4165,149,4207],"confinement","H4G4WeTzcsmG6glUDJrc_7H09bZb4hoavg3yFXRDN3A",{"id":4210,"title":4211,"author":8,"body":4212,"category":4164,"categorySlug":4165,"date":6081,"description":4216,"excerpt":6082,"extension":4199,"location":4200,"meta":6112,"navigation":211,"path":6113,"published":211,"seo":6114,"slug":6115,"stem":6116,"tags":6117,"timeToRead":549,"__hash__":6123},"posts\u002Fposts\u002FProgrammation\u002F2020-11-02_creation-api-2.md","Comment créer une bonne API Web - Partie 2",{"type":10,"value":4213,"toc":6069},[4214,4217,4220,4240,4244,4247,4250,4257,4261,4268,4278,4281,4286,4330,4335,4407,4412,4478,4481,4485,4488,4492,4530,4537,4544,4547,4551,4560,4570,4574,4579,4586,4593,4596,4604,4617,4627,4630,4642,4645,4659,4667,4681,4686,4689,4702,4711,4769,4772,4775,4790,4793,4799,5032,5035,5043,5049,5117,5120,5128,5136,5140,5145,5157,5164,5173,5177,5187,5195,5199,5205,5208,5211,5228,5231,5234,5237,5241,5245,5248,5254,5257,5260,5265,5268,5271,5274,5277,5291,5294,5297,5310,5313,5319,5323,5326,5332,5336,5340,5346,5349,5353,5359,5368,5372,5380,5386,5389,5393,5407,5410,5432,5436,5442,5447,5451,5455,5458,5461,5465,5468,5471,5475,5478,5481,5484,5487,5490,5494,5497,5501,5504,5508,5514,5518,5521,5525,5529,5532,5535,5539,5542,5546,5549,5553,5556,5559,5562,5576,5579,5582,5929,5932,5936,5939,5942,5945,5949,5952,5955,5970,5976,5980,5983,5986,5989,5993,6025,6066],[13,4215,4216],{},"Bonjour,",[13,4218,4219],{},"Cet article fait partie d'un ensemble:",[48,4221,4222,4228,4234],{},[51,4223,4224],{},[23,4225,4227],{"href":4226},"\u002Fpost\u002Fcreation-api-1\u002F","Généralités sur l'écriture d'une bonne API",[51,4229,4230],{},[23,4231,4233],{"href":4232},"\u002Fpost\u002Fcreation-api-2\u002F","Qu'est ce qu'une API REST",[51,4235,4236],{},[23,4237,4239],{"href":4238},"\u002Fpost\u002Fcreation-api-3\u002F","Qu'est ce qu'une API GraphQL",[64,4241,4243],{"id":4242},"quest-quune-api-rest","Qu'est qu'une API REST",[13,4245,4246],{},"REST est une norme dont voici les grandes lignes. Il n'est pas dans mon but de faire un cours sur REST (et il y en\ndéjà de très bons sur internet). Je souhaiterais surtout parler des points qui me semblent importants. N'hésitez pas à venir\nme dire si vous pensez qu'il manque des points importants.\nJe viendrai alors compléter mon article.",[13,4248,4249],{},"Le principe de REST est de séparer l'API en différentes ressources logiques qui peuvent être manipulées par les verbes\nHTTP (GET, POST, ...). La réponse de son côté se base également sur les codes http.",[13,4251,4252,4253,4256],{},"Le contenu de la requête et de la réponse peut être dans le format objet de votre choix (json, yaml, xml, ...). On\nutilise alors le header ",[151,4254,4255],{},"Content-Type"," pour définir le contenu. Une API peut d'ailleurs gérer plusieurs formats et\nrépondre au client le bon format en fonction de la demande du client.",[133,4258,4260],{"id":4259},"basé-sur-des-ressources","Basé sur des ressources",[13,4262,4263,4264,4267],{},"Quand on parle de REST, il faut penser ",[72,4265,4266],{},"ressources",". Mais qu'est qu'une ressource ?",[13,4269,4270,4271,4274,4275,4277],{},"Une ressource c'est un concept abstrait de REST. Une ressource c'est ce qui va être représenté par les données JSON\nque vous allez manipuler. Pour représenter une ressource on n'utilise pas un verbe, ou une action mais un ",[141,4272,4273],{},"nom",".\nL'utilisation du ",[141,4276,4273],{}," est important car le verbe est representé par la méthode HTTP.",[13,4279,4280],{},"Une ressource peut:",[48,4282,4283],{},[51,4284,4285],{},"être un singleton",[145,4287,4291],{"className":4288,"code":4289,"language":4290,"meta":104,"style":104},"language-json shiki shiki-themes one-dark-pro","GET \u002Fapi\u002Fhosts\u002F:id\n\n{\n  \"id\": 1,\n  ...\n}\n","json",[151,4292,4293,4298,4302,4307,4319,4325],{"__ignoreMap":104},[154,4294,4295],{"class":156,"line":157},[154,4296,4297],{"class":174},"GET \u002Fapi\u002Fhosts\u002F:id\n",[154,4299,4300],{"class":156,"line":178},[154,4301,212],{"emptyLinePlaceholder":211},[154,4303,4304],{"class":156,"line":208},[154,4305,4306],{"class":174},"{\n",[154,4308,4309,4312,4315,4317],{"class":156,"line":215},[154,4310,4311],{"class":193},"  \"id\"",[154,4313,4314],{"class":174},": ",[154,4316,106],{"class":228},[154,4318,1484],{"class":174},[154,4320,4321],{"class":156,"line":234},[154,4322,4324],{"class":4323},"sLaUg","  ...\n",[154,4326,4327],{"class":156,"line":256},[154,4328,4329],{"class":174},"}\n",[48,4331,4332],{},[51,4333,4334],{},"être une collection",[145,4336,4338],{"className":4288,"code":4337,"language":4290,"meta":104,"style":104},"GET \u002Fapi\u002Fhosts\n\n[\n  {\n    \"id\": 1,\n    ...\n  },\n  {\n    \"id\": 2,\n    ...\n  },\n]\n",[151,4339,4340,4345,4349,4354,4359,4370,4375,4380,4384,4394,4398,4402],{"__ignoreMap":104},[154,4341,4342],{"class":156,"line":157},[154,4343,4344],{"class":174},"GET \u002Fapi\u002Fhosts\n",[154,4346,4347],{"class":156,"line":178},[154,4348,212],{"emptyLinePlaceholder":211},[154,4350,4351],{"class":156,"line":208},[154,4352,4353],{"class":174},"[\n",[154,4355,4356],{"class":156,"line":215},[154,4357,4358],{"class":174},"  {\n",[154,4360,4361,4364,4366,4368],{"class":156,"line":234},[154,4362,4363],{"class":193},"    \"id\"",[154,4365,4314],{"class":174},[154,4367,106],{"class":228},[154,4369,1484],{"class":174},[154,4371,4372],{"class":156,"line":256},[154,4373,4374],{"class":4323},"    ...\n",[154,4376,4377],{"class":156,"line":374},[154,4378,4379],{"class":174},"  },\n",[154,4381,4382],{"class":156,"line":402},[154,4383,4358],{"class":174},[154,4385,4386,4388,4390,4392],{"class":156,"line":427},[154,4387,4363],{"class":193},[154,4389,4314],{"class":174},[154,4391,1447],{"class":228},[154,4393,1484],{"class":174},[154,4395,4396],{"class":156,"line":450},[154,4397,4374],{"class":4323},[154,4399,4400],{"class":156,"line":458},[154,4401,4379],{"class":174},[154,4403,4404],{"class":156,"line":463},[154,4405,4406],{"class":174},"]\n",[48,4408,4409],{},[51,4410,4411],{},"être une sous-ressource",[145,4413,4415],{"className":4288,"code":4414,"language":4290,"meta":104,"style":104},"GET \u002Fapi\u002Fhosts\u002F:id\u002Fbackups\n\n[\n  {\n    \"id\": 1,\n    ...\n  },\n  {\n    \"id\": 2,\n    ...\n  },\n]\n",[151,4416,4417,4422,4426,4430,4434,4444,4448,4452,4456,4466,4470,4474],{"__ignoreMap":104},[154,4418,4419],{"class":156,"line":157},[154,4420,4421],{"class":174},"GET \u002Fapi\u002Fhosts\u002F:id\u002Fbackups\n",[154,4423,4424],{"class":156,"line":178},[154,4425,212],{"emptyLinePlaceholder":211},[154,4427,4428],{"class":156,"line":208},[154,4429,4353],{"class":174},[154,4431,4432],{"class":156,"line":215},[154,4433,4358],{"class":174},[154,4435,4436,4438,4440,4442],{"class":156,"line":234},[154,4437,4363],{"class":193},[154,4439,4314],{"class":174},[154,4441,106],{"class":228},[154,4443,1484],{"class":174},[154,4445,4446],{"class":156,"line":256},[154,4447,4374],{"class":4323},[154,4449,4450],{"class":156,"line":374},[154,4451,4379],{"class":174},[154,4453,4454],{"class":156,"line":402},[154,4455,4358],{"class":174},[154,4457,4458,4460,4462,4464],{"class":156,"line":427},[154,4459,4363],{"class":193},[154,4461,4314],{"class":174},[154,4463,1447],{"class":228},[154,4465,1484],{"class":174},[154,4467,4468],{"class":156,"line":450},[154,4469,4374],{"class":4323},[154,4471,4472],{"class":156,"line":458},[154,4473,4379],{"class":174},[154,4475,4476],{"class":156,"line":463},[154,4477,4406],{"class":174},[13,4479,4480],{},"La ressource est généralement nommée au pluriel et pour des raisons de consistance, doit toujours être nommée de\nla même manière même si le but est de récupérer un unique élément.",[133,4482,4484],{"id":4483},"utilise-les-verbes-http","Utilise les verbes HTTP",[13,4486,4487],{},"Donc une API REST ce sont des ressources qui peuvent être manipulées par les verbes HTTP. Du coup l'action n'est pas\nportée par le nom de la ressource mais par la méthode. Quels sont donc ces méthodes ?",[4489,4490,4491],"h4",{"id":2293},"GET",[13,4493,4494,4496,4497,4499,4500,4503,4504,4511,4512,4514,4515,4522,4523,2877,4526,4529],{},[151,4495,4491],{}," est utilisé pour récupérer une ressource. En aucun cas ",[151,4498,4491],{}," doit être utilisé pour effectuer une modification\n(ce serait comme effectuer des modifications dans le ",[151,4501,4502],{},"getter"," d'une classe",[97,4505,4506],{},[23,4507,106],{"href":4508,"ariaDescribedBy":4509,"dataFootnoteRef":104,"id":4510},"#user-content-fn-getter",[103],"user-content-fnref-getter",").\n",[151,4513,4491],{}," ne doit pas modifier l'état de la ressource et doit être idempotent",[97,4516,4517],{},[23,4518,1447],{"href":4519,"ariaDescribedBy":4520,"dataFootnoteRef":104,"id":4521},"#user-content-fn-idempotent",[103],"user-content-fnref-idempotent",". On peut donc appeler autant de fois que\nl'on souhaite la méthode GET, le résultat doit toujours être le même tant qu'une autre méthode (",[151,4524,4525],{},"POST",[151,4527,4528],{},"PUT",", ...)\nne vient pas modifier l'état de la ressource.",[13,4531,4532,4533,4536],{},"Si la ressource peut être produite par le serveur, le code HTTP de réponse doit être ",[151,4534,4535],{},"200 OK"," et contenir le contenu de\nla ressource de la réponse dans le corps.",[13,4538,4539,4540,4543],{},"Le client peut alors cacher la requête s'il le souhaite, et il est possible d'utiliser les headers HTTP standards pour lui\nindiquer combien de temps (cf header Cache-Control). Attention ce header peut être ignoré ou peut être utilisé par le\nclient afin d'agir sur son ",[72,4541,4542],{},"rate limit",". A aucun moment il ne faut faire confiance au client et il faut préférer cacher\nles requêtes en interne (via un mêmcached) et définir une limite d'appels plutôt que de solliciter son back si ce\ndernier ne peut pas tenir la charge.",[13,4545,4546],{},"ne doit pas avoir d'impact\u002Fd'effet de bord dans l'application.",[4489,4548,4550],{"id":4549},"head","HEAD",[13,4552,4553,4554,4556,4557,4559],{},"La méthode ",[151,4555,4550],{}," a le même effet que ",[151,4558,4491],{}," mais ne retourne pas de contenu. Le header peut-être utilisé pour vérifier\nl'état de la ressource (et voir si le cache doit être invalidé).",[13,4561,4562,4563,4566,4567,298],{},"La méthode est considéré comme ",[72,4564,4565],{},"safe"," et ",[72,4568,4569],{},"idempotent",[4489,4571,4573],{"id":4572},"option","OPTION",[13,4575,4553,4576,4578],{},[151,4577,4573],{}," est utilisée pour vérifier les capacités de la méthode HTTP. Il est notament utilisé par les\nnavigateurs pour vérifier les entêtes CORS d'une API (en mode preflight) avant d'effectuer la véritable requête.",[13,4580,4581,4582,4585],{},"Pour interroger le serveur de facon générale ",[151,4583,4584],{},"*"," peut être utilisé à la place de l'URI. Ce seront donc les capacités\nglobales du serveur qui seront retournées",[13,4587,4588,4589,4566,4591,298],{},"La méthode est considérée comme ",[72,4590,4565],{},[72,4592,4569],{},[4489,4594,4525],{"id":4595},"post",[13,4597,4598,4600,4601,4603],{},[151,4599,4525],{}," doit être utilisé pour créer une nouvelle ressource associée à la requête demandée. ",[151,4602,4525],{}," est utilisé pour\najouter un nouvel élément à une collection.",[13,4605,4606,4607,4610,4611,4613,4614,4616],{},"Lorsque la ressource a été créé, la réponse doit être ",[151,4608,4609],{},"201 Created"," et le corps du message ne doit pas contenir\nd'informations. A la place on laisse le client récuperer la ressource en utilisant la méthode ",[151,4612,4491],{}," ci-dessus. Pour cela,\nle lien d'accès à la ressource doit être positionné dans le header ",[151,4615,355],{}," de la réponse HTTP.",[13,4618,4619,4620,4623,4624,4626],{},"Si toutefois la création de la ressource ne peut pas être liée à une ressource identifiable par un URI, la méthode peut\nalors retourner ",[151,4621,4622],{},"204 No Content"," ou ",[151,4625,4535],{}," suivant si la réponse possède un contenu ou non.",[13,4628,4629],{},"La réponse ne peut pas être cachée (sauf indication contraire par header HTTP).",[13,4631,4632,4633,4635,4636,4638,4639,4641],{},"Les requêtes ",[151,4634,4525],{}," ne sont pas ",[72,4637,4569],{}," et peuvent changer l'état de la ressource. Dans ce cas le client devra\nd'ailleurs invalider son cache. Deux appels identiques ",[151,4640,4525],{}," peuvent génerer des résultats différents.",[4489,4643,4528],{"id":4644},"put",[13,4646,4647,4648,4566,4650,4652,4653,4655,4656,4658],{},"La différence la plus importante entre ",[151,4649,4525],{},[151,4651,4528],{}," se situe sur l'URI. La méthode ",[151,4654,4528],{}," est utilisée sur les URI\navec un identifiant (et donc unique), alors que la méthode ",[151,4657,4525],{}," est utilisée sur URI représentant une collection.",[13,4660,4661,4663,4664,298],{},[151,4662,4528],{}," doit alors être utilisé pour mettre à jour une ressource existante (voir en créer une nouvelle si elle n'existe\npas). La ressource intégrale doit être passée. Pour une mise à jour partielle, il faut voir la méthode ",[151,4665,4666],{},"PATCH",[13,4668,4669,4670,4672,4673,4675,4676,4623,4678,4680],{},"S'il y a création d'une ressource, comme pour ",[151,4671,4525],{},", l'API doit retourner un ",[151,4674,4609],{},". Dans le cas d'une mise à\njour la méthode retourne alors ",[151,4677,4622],{},[151,4679,4535],{}," suivant que la réponse possède un contenu ou non.",[13,4682,4632,4683,4685],{},[151,4684,4528],{}," mettant à jour la ressource, le client devra invalider son cache sur la base de l'URI. La méthode est\nconsidérée comme idempotent car c'est la ressource entière qui est remplacée.",[4489,4687,4666],{"id":4688},"patch",[13,4690,4691,4693,4694,298],{},[151,4692,4666],{}," est utilisé pour mettre à jour une ressource partiellement. La mise à jour doit être également atomique",[97,4695,4696],{},[23,4697,4701],{"href":4698,"ariaDescribedBy":4699,"dataFootnoteRef":104,"id":4700},"#user-content-fn-atomic",[103],"user-content-fnref-atomic","3",[13,4703,4704,4705,4710],{},"Dans la RFC ",[23,4706,4709],{"href":4707,"rel":4708},"https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc5789",[27],"RFC 5789"," de la méthode PATCH, il y est écrit que le contenu doit\ncontenir la description des modifications :",[145,4712,4714],{"className":4288,"code":4713,"language":4290,"meta":104,"style":104},"PATCH \u002Ffile.txt HTTP\u002F1.1\nHost: www.example.com\nContent-Type: application\u002Fexample\nIf-Match: \"e0023aa4e\"\nContent-Length: 100\n\n[description of changes]\n",[151,4715,4716,4724,4729,4734,4742,4750,4754],{"__ignoreMap":104},[154,4717,4718,4721],{"class":156,"line":157},[154,4719,4720],{"class":174},"PATCH \u002Ffile.txt HTTP\u002F",[154,4722,4723],{"class":228},"1.1\n",[154,4725,4726],{"class":156,"line":178},[154,4727,4728],{"class":174},"Host: www.example.com\n",[154,4730,4731],{"class":156,"line":208},[154,4732,4733],{"class":174},"Content-Type: application\u002Fexample\n",[154,4735,4736,4739],{"class":156,"line":215},[154,4737,4738],{"class":174},"If-Match: ",[154,4740,4741],{"class":201},"\"e0023aa4e\"\n",[154,4743,4744,4747],{"class":156,"line":234},[154,4745,4746],{"class":174},"Content-Length: ",[154,4748,4749],{"class":228},"100\n",[154,4751,4752],{"class":156,"line":256},[154,4753,212],{"emptyLinePlaceholder":211},[154,4755,4756,4758,4761,4764,4767],{"class":156,"line":374},[154,4757,1271],{"class":174},[154,4759,4760],{"class":4323},"description",[154,4762,4763],{"class":4323}," of",[154,4765,4766],{"class":4323}," changes",[154,4768,4406],{"class":174},[13,4770,4771],{},"Utiliser une implémentation free-style de la description des changements risque d'être source d'erreurs pour le client\ncomme pour le serveur. En effet, le client pourrait s'attendre dans certains cas à des ajouts dans un tableau, ou au\nremplacement du tableau.",[13,4773,4774],{},"Il existe différentes RFC décrivant la bonne manière de faire pour PATCHer un document JSON. Il vaut mieux alors se baser\ndessus pour développer nos API. Cela permet d'être clair sur le fonctionnement de la méthode.",[13,4776,4777,4778,4783,4784,4789],{},"On peut alors se baser sur les ",[23,4779,4782],{"href":4780,"rel":4781},"https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc6902",[27],"RFC 6902"," et\n",[23,4785,4788],{"href":4786,"rel":4787},"https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc7396",[27],"RFC 7396"," pour décrire le contenu de cette description des modifications.",[13,4791,4792],{},"Pour les API basées sur des XML, référez-vous aux RFC équivalents décrivant les méthodes de PATCH pour le XML.",[13,4794,4795,4796,4798],{},"Si on se base sur la ",[151,4797,4782],{}," on peut alors reprendre l'exemple qui décrit un changement comme étant une liste de\ndifférences à appliquer (noté le Content-Type):",[145,4800,4802],{"className":4288,"code":4801,"language":4290,"meta":104,"style":104},"PATCH \u002Fmy\u002Fdata HTTP\u002F1.1\nHost: example.org\nContent-Length: 326\nContent-Type: application\u002Fjson-patch+json\nIf-Match: \"abc123\"\n\n[\n  { \"op\": \"test\", \"path\": \"\u002Fa\u002Fb\u002Fc\", \"value\": \"foo\" },\n  { \"op\": \"remove\", \"path\": \"\u002Fa\u002Fb\u002Fc\" },\n  { \"op\": \"add\", \"path\": \"\u002Fa\u002Fb\u002Fc\", \"value\": [ \"foo\", \"bar\" ] },\n  { \"op\": \"replace\", \"path\": \"\u002Fa\u002Fb\u002Fc\", \"value\": 42 },\n  { \"op\": \"move\", \"from\": \"\u002Fa\u002Fb\u002Fc\", \"path\": \"\u002Fa\u002Fb\u002Fd\" },\n  { \"op\": \"copy\", \"from\": \"\u002Fa\u002Fb\u002Fd\", \"path\": \"\u002Fa\u002Fb\u002Fe\" }\n]\n",[151,4803,4804,4811,4816,4823,4828,4835,4839,4843,4879,4900,4936,4966,4997,5028],{"__ignoreMap":104},[154,4805,4806,4809],{"class":156,"line":157},[154,4807,4808],{"class":174},"PATCH \u002Fmy\u002Fdata HTTP\u002F",[154,4810,4723],{"class":228},[154,4812,4813],{"class":156,"line":178},[154,4814,4815],{"class":174},"Host: example.org\n",[154,4817,4818,4820],{"class":156,"line":208},[154,4819,4746],{"class":174},[154,4821,4822],{"class":228},"326\n",[154,4824,4825],{"class":156,"line":215},[154,4826,4827],{"class":174},"Content-Type: application\u002Fjson-patch+json\n",[154,4829,4830,4832],{"class":156,"line":234},[154,4831,4738],{"class":174},[154,4833,4834],{"class":201},"\"abc123\"\n",[154,4836,4837],{"class":156,"line":256},[154,4838,212],{"emptyLinePlaceholder":211},[154,4840,4841],{"class":156,"line":374},[154,4842,4353],{"class":174},[154,4844,4845,4848,4851,4853,4856,4858,4861,4863,4866,4868,4871,4873,4876],{"class":156,"line":402},[154,4846,4847],{"class":174},"  { ",[154,4849,4850],{"class":193},"\"op\"",[154,4852,4314],{"class":174},[154,4854,4855],{"class":201},"\"test\"",[154,4857,2877],{"class":174},[154,4859,4860],{"class":193},"\"path\"",[154,4862,4314],{"class":174},[154,4864,4865],{"class":201},"\"\u002Fa\u002Fb\u002Fc\"",[154,4867,2877],{"class":174},[154,4869,4870],{"class":193},"\"value\"",[154,4872,4314],{"class":174},[154,4874,4875],{"class":201},"\"foo\"",[154,4877,4878],{"class":174}," },\n",[154,4880,4881,4883,4885,4887,4890,4892,4894,4896,4898],{"class":156,"line":427},[154,4882,4847],{"class":174},[154,4884,4850],{"class":193},[154,4886,4314],{"class":174},[154,4888,4889],{"class":201},"\"remove\"",[154,4891,2877],{"class":174},[154,4893,4860],{"class":193},[154,4895,4314],{"class":174},[154,4897,4865],{"class":201},[154,4899,4878],{"class":174},[154,4901,4902,4904,4906,4908,4911,4913,4915,4917,4919,4921,4923,4926,4928,4930,4933],{"class":156,"line":450},[154,4903,4847],{"class":174},[154,4905,4850],{"class":193},[154,4907,4314],{"class":174},[154,4909,4910],{"class":201},"\"add\"",[154,4912,2877],{"class":174},[154,4914,4860],{"class":193},[154,4916,4314],{"class":174},[154,4918,4865],{"class":201},[154,4920,2877],{"class":174},[154,4922,4870],{"class":193},[154,4924,4925],{"class":174},": [ ",[154,4927,4875],{"class":201},[154,4929,2877],{"class":174},[154,4931,4932],{"class":201},"\"bar\"",[154,4934,4935],{"class":174}," ] },\n",[154,4937,4938,4940,4942,4944,4947,4949,4951,4953,4955,4957,4959,4961,4964],{"class":156,"line":458},[154,4939,4847],{"class":174},[154,4941,4850],{"class":193},[154,4943,4314],{"class":174},[154,4945,4946],{"class":201},"\"replace\"",[154,4948,2877],{"class":174},[154,4950,4860],{"class":193},[154,4952,4314],{"class":174},[154,4954,4865],{"class":201},[154,4956,2877],{"class":174},[154,4958,4870],{"class":193},[154,4960,4314],{"class":174},[154,4962,4963],{"class":228},"42",[154,4965,4878],{"class":174},[154,4967,4968,4970,4972,4974,4977,4979,4982,4984,4986,4988,4990,4992,4995],{"class":156,"line":463},[154,4969,4847],{"class":174},[154,4971,4850],{"class":193},[154,4973,4314],{"class":174},[154,4975,4976],{"class":201},"\"move\"",[154,4978,2877],{"class":174},[154,4980,4981],{"class":193},"\"from\"",[154,4983,4314],{"class":174},[154,4985,4865],{"class":201},[154,4987,2877],{"class":174},[154,4989,4860],{"class":193},[154,4991,4314],{"class":174},[154,4993,4994],{"class":201},"\"\u002Fa\u002Fb\u002Fd\"",[154,4996,4878],{"class":174},[154,4998,4999,5001,5003,5005,5008,5010,5012,5014,5016,5018,5020,5022,5025],{"class":156,"line":485},[154,5000,4847],{"class":174},[154,5002,4850],{"class":193},[154,5004,4314],{"class":174},[154,5006,5007],{"class":201},"\"copy\"",[154,5009,2877],{"class":174},[154,5011,4981],{"class":193},[154,5013,4314],{"class":174},[154,5015,4994],{"class":201},[154,5017,2877],{"class":174},[154,5019,4860],{"class":193},[154,5021,4314],{"class":174},[154,5023,5024],{"class":201},"\"\u002Fa\u002Fb\u002Fe\"",[154,5026,5027],{"class":174}," }\n",[154,5029,5030],{"class":156,"line":499},[154,5031,4406],{"class":174},[13,5033,5034],{},"Le format à l'avantage d'être complet et standard.",[13,5036,5037,5038,2300],{},"Pour gérer le format dans l'application, on peut retrouver des librairies capables de lire et de générer ce genre de\ndifférenciel (par exemple ",[23,5039,5042],{"href":5040,"rel":5041},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Ffast-json-patch",[27],"fast-json-patch",[13,5044,5045,5046,5048],{},"Avec la ",[151,5047,4788],{},", on peut adapter le Content-Type et avoir un format de patch plus lisible :",[145,5050,5052],{"className":4288,"code":5051,"language":4290,"meta":104,"style":104},"PATCH \u002Ftarget HTTP\u002F1.1\nHost: example.org\nContent-Type: application\u002Fmerge-patch+json\n\n{\n  \"a\":\"z\",\n  \"c\": {\n    \"f\": null\n  }\n}\n",[151,5053,5054,5061,5065,5070,5074,5078,5090,5098,5108,5113],{"__ignoreMap":104},[154,5055,5056,5059],{"class":156,"line":157},[154,5057,5058],{"class":174},"PATCH \u002Ftarget HTTP\u002F",[154,5060,4723],{"class":228},[154,5062,5063],{"class":156,"line":178},[154,5064,4815],{"class":174},[154,5066,5067],{"class":156,"line":208},[154,5068,5069],{"class":174},"Content-Type: application\u002Fmerge-patch+json\n",[154,5071,5072],{"class":156,"line":215},[154,5073,212],{"emptyLinePlaceholder":211},[154,5075,5076],{"class":156,"line":234},[154,5077,4306],{"class":174},[154,5079,5080,5083,5085,5088],{"class":156,"line":256},[154,5081,5082],{"class":193},"  \"a\"",[154,5084,143],{"class":174},[154,5086,5087],{"class":201},"\"z\"",[154,5089,1484],{"class":174},[154,5091,5092,5095],{"class":156,"line":374},[154,5093,5094],{"class":193},"  \"c\"",[154,5096,5097],{"class":174},": {\n",[154,5099,5100,5103,5105],{"class":156,"line":402},[154,5101,5102],{"class":193},"    \"f\"",[154,5104,4314],{"class":174},[154,5106,5107],{"class":228},"null\n",[154,5109,5110],{"class":156,"line":427},[154,5111,5112],{"class":174},"  }\n",[154,5114,5115],{"class":156,"line":450},[154,5116,4329],{"class":174},[13,5118,5119],{},"Par contre ce format a ses limitations :",[48,5121,5122,5125],{},[51,5123,5124],{},"il ne peut que mettre à jour des éléments (pas de suppression)",[51,5126,5127],{},"dans le cadre de valeurs comme des tableaux, c'est le contenu intégral qui est remplacé. Pas d'ajout ou de merge.",[13,5129,5130],{},[141,5131,5132,5133,5135],{},"Il est alors très important de définir dans sa documentation la liste des ",[151,5134,4255],{}," accepté par sont API",[4489,5137,5139],{"id":5138},"delete","DELETE",[13,5141,4553,5142,5144],{},[151,5143,5139],{}," porte bien son nom car elle sert à supprimer une ressource identifiée par son URI.",[13,5146,5147,5148,5150,5151,5153,5154,298],{},"Si la suppression a été effectuée, l'API retourne un ",[151,5149,4535],{}," si le contenu de la réponse inclut un status de la\nsuppression ou un ",[151,5152,4622],{}," sinon. Si la ressource n'existe pas, l'API retourne alors un ",[151,5155,5156],{},"404 Not Found",[13,5158,5159,5160,5163],{},"L'API peut retourner un ",[151,5161,5162],{},"202 Accepted"," si la demande a été prise en compte mais sera traitée\nultérieurement.",[13,5165,5166,5167,5169,5170,5172],{},"Plusieurs appels à la méthode ",[151,5168,5139],{}," sur une même URI ne doivent pas avoir d'effets de bord. La seule différence est\nsur la réponse qui passe à ",[151,5171,5156],{}," une fois que la ressource a été supprimée.",[4489,5174,5176],{"id":5175},"link-et-unlink","LINK et UNLINK",[13,5178,5179,5180,4566,5183,5186],{},"Les méthodes ",[151,5181,5182],{},"LINK",[151,5184,5185],{},"UNLINK"," servent à lier\u002Fdélier des ressources entre elles.",[13,5188,5189,5190,298],{},"Je ne détaillerai pas leurs utilisations car elles sont peut utilisées. Vous pouvez en lire plus sur le site de\nl'",[23,5191,5194],{"href":5192,"rel":5193},"https:\u002F\u002Ftools.ietf.org\u002Fid\u002Fdraft-snell-link-method-01.html",[27],"IETF",[133,5196,5198],{"id":5197},"respecter-les-codes-retour-http","Respecter les codes retour HTTP",[13,5200,5201,5202,5204],{},"La norme REST se base sur les code de retour HTTP. Par exemple un code HTTP ",[151,5203,5156],{}," signifie que la ressource ne\npeut pas être trouvée. Il est important pour les applications clientes de respecter ses codes HTTP.",[13,5206,5207],{},"Commençons par un petit rappel sur les codes de retour.",[13,5209,5210],{},"Ces derniers sont classés en 5 catégories:",[48,5212,5213,5216,5219,5222,5225],{},[51,5214,5215],{},"1xx: Informationnel. Ces codes HTTP sont utilisés pour communiquer des informations sur l'état du transfert à un\nniveau protocolaire (donc pas au niveau applicatif).",[51,5217,5218],{},"2xx: Succès. Ces codes HTTP sont utilisés pour signifier le succès de la requête.",[51,5220,5221],{},"3xx: Redirection. Le client doit prendre des actions supplémentaires afin de terminer la requête.",[51,5223,5224],{},"4xx: Erreur dûe à la requête du client. C'est de la faute du client si la réponse ne peut pas être fournie. Il doit\nla reformuler (si la requête est mauvaise) ou attendre dans le cas d'un rate limit.",[51,5226,5227],{},"5xx: Erreur dûe au serveur. Le serveur n'a pas réussi à répondre au client, et c'est de son entière responsabilité.",[13,5229,5230],{},"Il est important de bien choisir son code de retour pour que le client puisse bien comprendre quelles actions il peut\nentreprendre pour avoir sa réponse. Envoyer un code 5xx alors que la requête ne contient pas tout les champs\nobligatoires est le meilleur moyen pour remplir votre boîte de support.",[13,5232,5233],{},"Inversement renvoyer un code 4xx pour une erreur côté serveur, risque de perturber l'utilisateur qui ne comprendra pas\npourquoi l'API ne fonctionne pas comme attendu.",[13,5235,5236],{},"Passons en revue quelques exemples de codes utilisables (j'ai fait uniquement une sélection de codes qui peuvent avoir un intérêt\npour une API REST) :",[4489,5238,5240],{"id":5239},"_2xx","2xx",[5242,5243,4535],"h5",{"id":5244},"_200-ok",[13,5246,5247],{},"Parfait, pas de problème, c'est le succès complet.",[13,5249,5250,5251,5253],{},"Contrairement à la réponse 204 ci-dessous, il est nécessaire de fournir un corps au message (sauf pour le header\n",[151,5252,4550],{}," qui ne renvoit jamais de corps de message).",[5242,5255,4609],{"id":5256},"_201-created",[13,5258,5259],{},"Une nouvelle ressource a été créé avec succès et ajoutée à la collection. Le corps du message doit être vide.\nLe client peut invalider le cache de la collection.",[13,5261,5262,5263,298],{},"Ce code de retour doit être utilisé une fois que la ressource a été créé par le serveur et l'emplacement de celle-ci\ndoit être retourné dans le header HTTP ",[151,5264,355],{},[13,5266,5267],{},"Si la ressource ne peut être créé tout de suite, alors voir le header suivant.",[5242,5269,5162],{"id":5270},"_202-accepted",[13,5272,5273],{},"La requête a bien été reçue et sera traitée ultérieurement (via un batch par exemple). La création peut se produire, ou\nune erreur peut arriver ultérieurement.",[13,5275,5276],{},"Dans le corps du message le serveur doit retourner au client le maximum d'informations qui permettent à ce dernier de\nsavoir quand sa demande sera traitée. Par exemple on peut mettre dans le corps du message :",[48,5278,5279,5282,5285,5288],{},[51,5280,5281],{},"Le statut de la demande",[51,5283,5284],{},"La date d'exécution du process",[51,5286,5287],{},"L'URI pour accéder à la demande et obtenir son statut",[51,5289,5290],{},"Une estimation du temps de traitement.",[5242,5292,4622],{"id":5293},"_204-no-content",[13,5295,5296],{},"Le serveur a bien traité la demande mais aucune information ne sera retournée dans le body.",[13,5298,5299,5300,2877,5302,2877,5304,5306,5307,5309],{},"Ce code HTTP est généralement utilisé avec les méthodes ",[151,5301,4525],{},[151,5303,4528],{},[151,5305,5139],{}," quand le serveur n'a aucune information\n(au dela du header Location) à retourner au client. Il peut également être utilisé avec les méthodes ",[151,5308,4491],{}," pour indiquer\nque la ressource existe (contrairement au 404) mais aucune représentation de la ressource n'existe.",[13,5311,5312],{},"La réponse ne doit jamais contenir de contenu.",[13,5314,5315,5316,5318],{},"Sauf si un header (tel que ",[151,5317,355],{},") l'indique, le client n'a pas besoin de modifier son cache.",[5242,5320,5322],{"id":5321},"_206-partial-content","206 Partial Content",[13,5324,5325],{},"Utilisé avec le header Range dans la requête pour n'envoyer qu'un résultat partiel.",[13,5327,5328,5329,298],{},"Cette réponse peut-être utilisée dans les requêtes de pagination en conjonction uniquement avec le header ",[151,5330,5331],{},"Range",[4489,5333,5335],{"id":5334},"_3xx","3xx",[5242,5337,5339],{"id":5338},"_301-moved-permanently","301 Moved Permanently",[13,5341,5342,5343,5345],{},"La ressource a été déplacée définitivement à un autre endroit. Le header ",[151,5344,355],{}," contient le nouvel emplacement. Le\nrésultat de la requête peut être caché.",[13,5347,5348],{},"On peut utiliser ce retour pour signifier un changement de l'API suffisamment important pour que cette version ne soit\nplus à jour, mais il est préférable de versionner l'API, plutôt que d'utiliser les redirections.",[5242,5350,5352],{"id":5351},"_302-found","302 Found",[13,5354,5355,5356,5358],{},"La ressource a été déplacée temporairement à un autre endroit. Le header ",[151,5357,355],{}," contient l'emplacement. La requête\nne peut être cachée que si les headers de caches sont présents.",[13,5360,5361,5362,5364,5365,5367],{},"Le client doit refaire exactement la même requête mais sur le nouvel emplacement défini dans le header ",[151,5363,355],{},".\nCette règle n'est pas respectée par les navigateurs qui remplacent la méthode par ",[151,5366,4491],{}," et c'est pour ça que les codes\n303 et 307 ont été créés.",[5242,5369,5371],{"id":5370},"_303-see-other","303 See Other",[13,5373,5374,5375,4623,5377,5379],{},"Ce code de retour, généralement utilisé avec des ",[151,5376,4528],{},[151,5378,4525],{}," indique que le serveur a terminé son travail mais\npréfère retourner au client une URI où trouver la ressource ou un message de status.",[13,5381,5382,5383,5385],{},"Le client peut alors décider ou non d'aller voir à l'adresse donnée via la méthode ",[151,5384,4491],{}," uniquement.",[13,5387,5388],{},"La réponse ne peut pas être modifiée.",[5242,5390,5392],{"id":5391},"_304-not-modified","304 Not Modified",[13,5394,5395,5396,4623,5398,5400,5401,4566,5404,2300],{},"Utilisé généralement avec une requête ",[151,5397,4491],{},[151,5399,4550],{}," pour indiquer que la ressource n'a pas été modifiée par rapport aux\ninformations spécifiées dans le header (",[151,5402,5403],{},"If-None-Match",[151,5405,5406],{},"If-Modified-Since",[13,5408,5409],{},"Cette réponse est utilisée pour économiser du temps de traitement et de la bande passsante côté serveur et côté client,\nvu que le client peut reprendre la réponse qui se présente dans son cache.",[13,5411,5412,5413,1484,5416,2877,5419,2877,5422,2877,5425,5428,5429,298],{},"Généralement, une réponse précédente a été déjà envoyée au client avec les headers suivants: ",[151,5414,5415],{},"Cache-Control",[151,5417,5418],{},"Content-Location",[151,5420,5421],{},"Date",[151,5423,5424],{},"ETag",[151,5426,5427],{},"Expires",", et ",[151,5430,5431],{},"Vary",[5242,5433,5435],{"id":5434},"_307-temporary-redirect","307 Temporary Redirect",[13,5437,5438,5439,5441],{},"Le serveur indique que la ressource doit être récupérée à un autre endroit en ré-envoyant la requête telle quelle à\nl'URI spécifiée dans le header ",[151,5440,355],{},". Toute future requête doit continuer à être envoyée au endpoint actuel.",[13,5443,5444,5445,298],{},"La différence entre une réponse 302 et 307 réside dans le fait que la requête ne doit pas être modifiée lors de l'appel\nà l'URI de la réponse retournée par la 307. Avec une 302, certains clients changent incorectement cette valeur en ",[151,5446,4491],{},[4489,5448,5450],{"id":5449},"_4xx","4xx",[5242,5452,5454],{"id":5453},"_400-bad-request","400 Bad Request",[13,5456,5457],{},"Le serveur ne comprend pas la requête à cause de sa syntaxe. Le client ne doit pas répéter sa requête sans\nmodification.",[13,5459,5460],{},"Ce message peut être utilisé quand aucun autre message de type 4xx n'est approprié.",[5242,5462,5464],{"id":5463},"_401-unauthorized","401 Unauthorized",[13,5466,5467],{},"La ressource nécessite une authentification. Le client peut répéter sa requête s'il possède le bon header\nd'Authorization.",[13,5469,5470],{},"Si le header d'authentification est présent mais que le serveur retourne une 401, cela signifie que\nl'authentification a été refusée avec ces paramètres.",[5242,5472,5474],{"id":5473},"_403-forbidden","403 Forbidden",[13,5476,5477],{},"L'authentification du client est bien reconnue par le serveur, mais le client ne possède pas les autorisations\nnécessaires pour accéder à la ressource.",[13,5479,5480],{},"Le client ne doit pas répéter la demande.",[5242,5482,5156],{"id":5483},"_404-not-found",[13,5485,5486],{},"La ressource n'existe pas sur le serveur, pour le moment. Le client peut potentiellement refaire la requête plus tard.",[13,5488,5489],{},"Ce status est également utilisé lorsque le serveur ne veut pas révéler pourquoi la requête a été refusée.",[5242,5491,5493],{"id":5492},"_409-conflict","409 Conflict",[13,5495,5496],{},"La requête est bien formée (contrairement à 400) mais elle ne peut pas être complétée dû à l'état de la ressource.",[5242,5498,5500],{"id":5499},"_410-gone","410 Gone",[13,5502,5503],{},"La ressource n'existe pas sur le serveur, mais le serveur sait (par un moyen interne) que la ressource a existé par le\npassé et qu'il n'est pas dans la capacité de fournir une URI où trouver la nouvelle version.",[5242,5505,5507],{"id":5506},"_416-requested-range-not-satisfiable","416 Requested Range Not Satisfiable",[13,5509,5510,5511,5513],{},"L'intervalle utilisé dans le header ",[151,5512,5331],{}," ne peut pas être satisfait.",[5242,5515,5517],{"id":5516},"_429-too-many-requests","429 Too Many Requests",[13,5519,5520],{},"Utilisé pour prévenir le client qu'il a atteint ses limites (rate limit)",[4489,5522,5524],{"id":5523},"_5xx","5xx",[5242,5526,5528],{"id":5527},"_500-internal-server-error","500 Internal Server Error",[13,5530,5531],{},"Le serveur clame haut et fort \"Une erreur inattendue s’est produite\".",[13,5533,5534],{},"L'erreur 500 n'est jamais de la faute du client. Le client peut donc retenter sa requête plus tard.",[5242,5536,5538],{"id":5537},"_501-not-implemented","501 Not Implemented",[13,5540,5541],{},"Pratique pour les API en cours de développement, l'API existe mais n'a pas encore été implémentée.",[5242,5543,5545],{"id":5544},"_503-service-unavailable","503 Service Unavailable",[13,5547,5548],{},"Le serveur n'est pas encore prêt à répondre à la requête (par exemple il démarre ou il s'arrête).",[133,5550,5552],{"id":5551},"utilise-la-notion-de-lien-hypermedia-hateoas","Utilise la notion de lien hypermedia (HATEOAS)",[13,5554,5555],{},"HATEOAS, c'est pour Hypermedia As The Engine Of Application State.",[13,5557,5558],{},"Peu d'API sont REST HATEOAS. Le but est de REST HATEOAS est de lier les réponses REST les unes aux autres tout comme\nle Web lie les pages les unes aux autres. Le principe est donc d'ajouter la notion de lien hypermédia dans une API.",[13,5560,5561],{},"Dans la théorie, un client pourra donc s'adapter aux changement de l'API grâce à ces liens.",[13,5563,5564,5565,5570,5571,298],{},"Pour cela vous pouvez pouvez dans cadre du JSON vous baser sur\n",[23,5566,5569],{"href":5567,"rel":5568},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHypertext_Application_Language",[27],"HAL"," ou sur ",[23,5572,5575],{"href":5573,"rel":5574},"https:\u002F\u002Fjsonapi.org\u002F",[27],"JSON:API",[4489,5577,5575],{"id":5578},"jsonapi",[13,5580,5581],{},"Voici un exemple utilisant la syntaxe JSON:API.",[145,5583,5585],{"className":4288,"code":5584,"language":4290,"meta":104,"style":104},"{\n  \"links\": {\n    \"self\": \"\u002Fapi\u002Fhosts\",\n    \"next\": \"\u002Fapi\u002Fhosts?page[offset]=2\",\n    \"last\": \"\u002Fapi\u002Fhosts?page[offset]=10\"\n  },\n  \"data\": [\n    {\n      \"type\": \"hosts\",\n      \"id\": \"pc-ulrich\",\n      \"attributes\": {\n        \"dhcp\": [\n          {\n            \"address\": \"192.168.101\",\n            \"start\": 0,\n            \"end\": 50\n          }\n        ]\n      },\n      \"relationships\": {\n        \"backups\": {\n          \"links\": {\n            \"self\": \"\u002Fapi\u002Fhosts\u002Fpc-ulrich\u002Frelationships\u002Fbackups\",\n            \"related\": \"\u002Fapi\u002Fhosts\u002Fpc-ulrich\u002Fbackups\"\n          },\n          \"data\": [\n            { \"type\": \"backups\", \"id\": 0 },\n            { \"type\": \"backups\", \"id\": 1 },\n            { \"type\": \"backups\", \"id\": 2 },\n            { \"type\": \"backups\", \"id\": 3 }\n          ]\n        }\n      },\n      \"links\": {\n        \"self\": \"\u002Fhosts\u002Fpc-ulrich\"\n      }\n    }\n  ]\n}\n",[151,5586,5587,5591,5598,5610,5622,5632,5636,5644,5649,5661,5673,5680,5687,5692,5704,5715,5725,5730,5735,5740,5747,5754,5761,5773,5783,5788,5795,5819,5839,5859,5879,5884,5888,5892,5899,5909,5914,5918,5924],{"__ignoreMap":104},[154,5588,5589],{"class":156,"line":157},[154,5590,4306],{"class":174},[154,5592,5593,5596],{"class":156,"line":178},[154,5594,5595],{"class":193},"  \"links\"",[154,5597,5097],{"class":174},[154,5599,5600,5603,5605,5608],{"class":156,"line":208},[154,5601,5602],{"class":193},"    \"self\"",[154,5604,4314],{"class":174},[154,5606,5607],{"class":201},"\"\u002Fapi\u002Fhosts\"",[154,5609,1484],{"class":174},[154,5611,5612,5615,5617,5620],{"class":156,"line":215},[154,5613,5614],{"class":193},"    \"next\"",[154,5616,4314],{"class":174},[154,5618,5619],{"class":201},"\"\u002Fapi\u002Fhosts?page[offset]=2\"",[154,5621,1484],{"class":174},[154,5623,5624,5627,5629],{"class":156,"line":234},[154,5625,5626],{"class":193},"    \"last\"",[154,5628,4314],{"class":174},[154,5630,5631],{"class":201},"\"\u002Fapi\u002Fhosts?page[offset]=10\"\n",[154,5633,5634],{"class":156,"line":256},[154,5635,4379],{"class":174},[154,5637,5638,5641],{"class":156,"line":374},[154,5639,5640],{"class":193},"  \"data\"",[154,5642,5643],{"class":174},": [\n",[154,5645,5646],{"class":156,"line":402},[154,5647,5648],{"class":174},"    {\n",[154,5650,5651,5654,5656,5659],{"class":156,"line":427},[154,5652,5653],{"class":193},"      \"type\"",[154,5655,4314],{"class":174},[154,5657,5658],{"class":201},"\"hosts\"",[154,5660,1484],{"class":174},[154,5662,5663,5666,5668,5671],{"class":156,"line":450},[154,5664,5665],{"class":193},"      \"id\"",[154,5667,4314],{"class":174},[154,5669,5670],{"class":201},"\"pc-ulrich\"",[154,5672,1484],{"class":174},[154,5674,5675,5678],{"class":156,"line":458},[154,5676,5677],{"class":193},"      \"attributes\"",[154,5679,5097],{"class":174},[154,5681,5682,5685],{"class":156,"line":463},[154,5683,5684],{"class":193},"        \"dhcp\"",[154,5686,5643],{"class":174},[154,5688,5689],{"class":156,"line":485},[154,5690,5691],{"class":174},"          {\n",[154,5693,5694,5697,5699,5702],{"class":156,"line":499},[154,5695,5696],{"class":193},"            \"address\"",[154,5698,4314],{"class":174},[154,5700,5701],{"class":201},"\"192.168.101\"",[154,5703,1484],{"class":174},[154,5705,5706,5709,5711,5713],{"class":156,"line":504},[154,5707,5708],{"class":193},"            \"start\"",[154,5710,4314],{"class":174},[154,5712,926],{"class":228},[154,5714,1484],{"class":174},[154,5716,5717,5720,5722],{"class":156,"line":509},[154,5718,5719],{"class":193},"            \"end\"",[154,5721,4314],{"class":174},[154,5723,5724],{"class":228},"50\n",[154,5726,5727],{"class":156,"line":526},[154,5728,5729],{"class":174},"          }\n",[154,5731,5732],{"class":156,"line":549},[154,5733,5734],{"class":174},"        ]\n",[154,5736,5737],{"class":156,"line":563},[154,5738,5739],{"class":174},"      },\n",[154,5741,5742,5745],{"class":156,"line":569},[154,5743,5744],{"class":193},"      \"relationships\"",[154,5746,5097],{"class":174},[154,5748,5749,5752],{"class":156,"line":786},[154,5750,5751],{"class":193},"        \"backups\"",[154,5753,5097],{"class":174},[154,5755,5756,5759],{"class":156,"line":797},[154,5757,5758],{"class":193},"          \"links\"",[154,5760,5097],{"class":174},[154,5762,5763,5766,5768,5771],{"class":156,"line":818},[154,5764,5765],{"class":193},"            \"self\"",[154,5767,4314],{"class":174},[154,5769,5770],{"class":201},"\"\u002Fapi\u002Fhosts\u002Fpc-ulrich\u002Frelationships\u002Fbackups\"",[154,5772,1484],{"class":174},[154,5774,5775,5778,5780],{"class":156,"line":838},[154,5776,5777],{"class":193},"            \"related\"",[154,5779,4314],{"class":174},[154,5781,5782],{"class":201},"\"\u002Fapi\u002Fhosts\u002Fpc-ulrich\u002Fbackups\"\n",[154,5784,5785],{"class":156,"line":854},[154,5786,5787],{"class":174},"          },\n",[154,5789,5790,5793],{"class":156,"line":882},[154,5791,5792],{"class":193},"          \"data\"",[154,5794,5643],{"class":174},[154,5796,5797,5800,5803,5805,5808,5810,5813,5815,5817],{"class":156,"line":887},[154,5798,5799],{"class":174},"            { ",[154,5801,5802],{"class":193},"\"type\"",[154,5804,4314],{"class":174},[154,5806,5807],{"class":201},"\"backups\"",[154,5809,2877],{"class":174},[154,5811,5812],{"class":193},"\"id\"",[154,5814,4314],{"class":174},[154,5816,926],{"class":228},[154,5818,4878],{"class":174},[154,5820,5821,5823,5825,5827,5829,5831,5833,5835,5837],{"class":156,"line":892},[154,5822,5799],{"class":174},[154,5824,5802],{"class":193},[154,5826,4314],{"class":174},[154,5828,5807],{"class":201},[154,5830,2877],{"class":174},[154,5832,5812],{"class":193},[154,5834,4314],{"class":174},[154,5836,106],{"class":228},[154,5838,4878],{"class":174},[154,5840,5841,5843,5845,5847,5849,5851,5853,5855,5857],{"class":156,"line":899},[154,5842,5799],{"class":174},[154,5844,5802],{"class":193},[154,5846,4314],{"class":174},[154,5848,5807],{"class":201},[154,5850,2877],{"class":174},[154,5852,5812],{"class":193},[154,5854,4314],{"class":174},[154,5856,1447],{"class":228},[154,5858,4878],{"class":174},[154,5860,5861,5863,5865,5867,5869,5871,5873,5875,5877],{"class":156,"line":910},[154,5862,5799],{"class":174},[154,5864,5802],{"class":193},[154,5866,4314],{"class":174},[154,5868,5807],{"class":201},[154,5870,2877],{"class":174},[154,5872,5812],{"class":193},[154,5874,4314],{"class":174},[154,5876,4701],{"class":228},[154,5878,5027],{"class":174},[154,5880,5881],{"class":156,"line":921},[154,5882,5883],{"class":174},"          ]\n",[154,5885,5886],{"class":156,"line":931},[154,5887,566],{"class":174},[154,5889,5890],{"class":156,"line":946},[154,5891,5739],{"class":174},[154,5893,5894,5897],{"class":156,"line":961},[154,5895,5896],{"class":193},"      \"links\"",[154,5898,5097],{"class":174},[154,5900,5901,5904,5906],{"class":156,"line":976},[154,5902,5903],{"class":193},"        \"self\"",[154,5905,4314],{"class":174},[154,5907,5908],{"class":201},"\"\u002Fhosts\u002Fpc-ulrich\"\n",[154,5910,5911],{"class":156,"line":1000},[154,5912,5913],{"class":174},"      }\n",[154,5915,5916],{"class":156,"line":1005},[154,5917,367],{"class":174},[154,5919,5921],{"class":156,"line":5920},38,[154,5922,5923],{"class":174},"  ]\n",[154,5925,5927],{"class":156,"line":5926},39,[154,5928,4329],{"class":174},[13,5930,5931],{},"Comme vous pouvez voir, l'idée est de pouvoir à la lecture de la réponse comprendre automatiquement les relations\nentre les différents composants de l'API.",[4489,5933,5935],{"id":5934},"faut-il-faire-une-api-hateaos","Faut-il faire une API HATEAOS",[13,5937,5938],{},"Le but d'une telle API est de permettre au client de la découvrir automatiquement. Il serait envisageable alors de\ncréer un client capable, sur la base de l'API et des types de données qui y sont décrits, de pouvoir génerer une IHM\ndirectement.",[13,5940,5941],{},"Même si le principe d'une API HATEAOS est louable, le temps d'écriture d'une telle API est très consommatrice de temps.\nDe plus, généralement, un client est généralement créé pour des utilisateurs et non par une API.",[13,5943,5944],{},"Une bonne documentation peut amplement suffir pour développer une application, et comprendre les interactions entre\nles différentes APIs ?",[133,5946,5948],{"id":5947},"générér-sa-documentation-avec-swagger","Générér sa documentation avec Swagger",[13,5950,5951],{},"Il existe de très bon outils pour écrire sa documentation.",[13,5953,5954],{},"L'un d'entre eux s'appelle Swagger basé sur la spécification OpenAPI. Il y a alors plusieurs manières de faire pour\ngénérer sa documention qui dépendent également des capacités du language.",[48,5956,5957,5960,5967],{},[51,5958,5959],{},"Sur un language typé, on peut générer sa documentation à partir du code. Il est alors possible via des annotations\nou des commentaires du code de définir les informations.",[51,5961,5962,5963,5966],{},"Il est également possible d'écrire le Swagger directement, et de générer le code après coup. Cela à l'avantage de\npouvoir travailler l'API avant sans écrire une ligne de code.",[5964,5965],"br",{},"L'important sur ces deux méthodes c'est que le code de l'application et le code du Swagger soient synchronisé.",[51,5968,5969],{},"Si l'API a déjà été développée, il est beaucoup plus dur de générer une API par dessus. Surtout si le language est\nnon typé, et que toute la description doit être synchronisée à la main avec les objets de l'application, il y aura\nalors un risque que la documentation ne soit pas à jour.",[13,5971,5972],{},[123,5973],{"alt":5974,"src":5975},"Swagger WoodstockBackup","\u002FProgrammation\u002Fcreation-api\u002Fdocumentation-swagger.png",[133,5977,5979],{"id":5978},"cacher-ses-requêtes","Cacher ses requêtes",[13,5981,5982],{},"Il est important côté client de cacher les requêtes afin de rester en dessous du Rate Limit, et aussi d'économiser les\nressources de bande passante de l'application.",[13,5984,5985],{},"Comme dit plus haut par contre, il ne faut pas que le serveur prenne comme hypothèse que le client va cacher les\nrequêtes au risque de se faire saturer par des requêtes. Côté serveur, il faut définir des limites d'accès de l'API,\net surtout définir également les headers de Cache qui vont bien afin d'avertir le client de la durée pendant laquelle\nil doit garder la requête.",[13,5987,5988],{},"En effet seul le serveur connaît fonctionnellement combien de temps une donnée peut être gardée.",[133,5990,5992],{"id":5991},"quelques-références","Quelques références",[48,5994,5995,6001,6007,6013,6019],{},[51,5996,5997],{},[23,5998,5999],{"href":5999,"rel":6000},"https:\u002F\u002Fzestedesavoir.com\u002Ftutoriels\u002F299\u002Fla-theorie-rest-restful-et-hateoas\u002F",[27],[51,6002,6003],{},[23,6004,6005],{"href":6005,"rel":6006},"https:\u002F\u002Fwww.vinaysahni.com\u002Fbest-practices-for-a-pragmatic-restful-api",[27],[51,6008,6009],{},[23,6010,6011],{"href":6011,"rel":6012},"https:\u002F\u002Fstackoverflow.blog\u002F2020\u002F03\u002F02\u002Fbest-practices-for-rest-api-design\u002F",[27],[51,6014,6015],{},[23,6016,6017],{"href":6017,"rel":6018},"https:\u002F\u002Frestfulapi.net\u002Fhttp-methods\u002F",[27],[51,6020,6021],{},[23,6022,6023],{"href":6023,"rel":6024},"https:\u002F\u002Fwilliamdurand.fr\u002F2014\u002F02\u002F14\u002Fplease-do-not-patch-like-an-idiot\u002F",[27],[4118,6026,6028,6031],{"className":6027,"dataFootnotes":104},[4121],[64,6029,4126],{"className":6030,"id":103},[4125],[4128,6032,6033,6044,6057],{},[51,6034,6036,6037,6039,6040],{"id":6035},"user-content-fn-getter","Ce que j'ai déjà vu faire. Pour préciser les choses, un getter doit être ",[72,6038,4565],{},". Cela signifie que l'appel ",[23,6041,4140],{"href":6042,"ariaLabel":4137,"className":6043,"dataFootnoteBackref":104},"#user-content-fnref-getter",[4139],[51,6045,6047,6052,6053],{"id":6046},"user-content-fn-idempotent",[23,6048,6051],{"href":6049,"rel":6050},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FIdempotence#:~:text=En%20math%C3%A9matiques%20et%20en%20informatique,applique%20une%20ou%20plusieurs%20fois.",[27],"En mathématiques et en informatique, l'idempotence signifie qu'une opération a le même effet qu'on l'applique une ou plusieurs fois.",": Dans notre cas cela signifie qu'elles n'ont pas d'effets de bord. ",[23,6054,4140],{"href":6055,"ariaLabel":4148,"className":6056,"dataFootnoteBackref":104},"#user-content-fnref-idempotent",[4139],[51,6058,6060,6061],{"id":6059},"user-content-fn-atomic","Ce qui veux dire que tout est fait ou rien. ",[23,6062,4140],{"href":6063,"ariaLabel":6064,"className":6065,"dataFootnoteBackref":104},"#user-content-fnref-atomic","Back to reference 3",[4139],[4151,6067,6068],{},"html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html pre.shiki code .sLaUg, html code.shiki .sLaUg{--shiki-default:#FFFFFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}",{"title":104,"searchDepth":178,"depth":178,"links":6070},[6071,6080],{"id":4242,"depth":178,"text":4243,"children":6072},[6073,6074,6075,6076,6077,6078,6079],{"id":4259,"depth":208,"text":4260},{"id":4483,"depth":208,"text":4484},{"id":5197,"depth":208,"text":5198},{"id":5551,"depth":208,"text":5552},{"id":5947,"depth":208,"text":5948},{"id":5978,"depth":208,"text":5979},{"id":5991,"depth":208,"text":5992},{"id":103,"depth":178,"text":4126},"2020-11-02",{"type":10,"value":6083},[6084,6086,6088,6102,6104,6106,6108],[13,6085,4216],{},[13,6087,4219],{},[48,6089,6090,6094,6098],{},[51,6091,6092],{},[23,6093,4227],{"href":4226},[51,6095,6096],{},[23,6097,4233],{"href":4232},[51,6099,6100],{},[23,6101,4239],{"href":4238},[64,6103,4243],{"id":4242},[13,6105,4246],{},[13,6107,4249],{},[13,6109,4252,6110,4256],{},[151,6111,4255],{},{"planet":211},"\u002Fpost\u002Fcreation-api-2",{"title":4211,"description":4216},"creation-api-2","posts\u002FProgrammation\u002F2020-11-02_creation-api-2",[6118,6119,6120,6121,6122],"api","graphql","rest","javascript","nodejs","qRIJgww-9nhPwt3g0Lbn9M52urBRQ1PwFy4IGXjFHQI",{"id":6125,"title":6126,"author":8,"body":6127,"category":4164,"categorySlug":4165,"date":6991,"description":4216,"excerpt":6992,"extension":4199,"location":4200,"meta":7016,"navigation":211,"path":7017,"published":211,"seo":7018,"slug":7019,"stem":7020,"tags":7021,"timeToRead":458,"__hash__":7022},"posts\u002Fposts\u002FProgrammation\u002F2020-10-11_creation-api-1.md","Comment créer une bonne API Web - Partie 1",{"type":10,"value":6128,"toc":6981},[6129,6131,6134,6148,6151,6154,6157,6160,6166,6183,6186,6203,6206,6210,6221,6224,6230,6233,6236,6239,6242,6245,6248,6252,6255,6258,6263,6266,6286,6289,6317,6324,6327,6331,6334,6337,6357,6360,6363,6457,6468,6472,6475,6488,6603,6609,6612,6717,6720,6726,6851,6855,6858,6908,6912,6915,6918,6921,6924,6927,6930,6952,6955,6959,6962,6978],[13,6130,4216],{},[13,6132,6133],{},"Je souhaite vous parler de l'écriture d'API. Je vais découper cet article en 3 parties:",[48,6135,6136,6140,6144],{},[51,6137,6138],{},[23,6139,4227],{"href":4226},[51,6141,6142],{},[23,6143,4233],{"href":4232},[51,6145,6146],{},[23,6147,4239],{"href":4238},[13,6149,6150],{},"Je me limiterai au WEB et aux normes REST et GraphQL même s'il y a d'autres normes\u002Fframeworks pour écrire des API.",[13,6152,6153],{},"Commençons donc par le début ! Qu'est-ce qu'une API ? API signifie Application Programming Interface. C'est une\ninterface de programmation prête à être consommée par un client.",[13,6155,6156],{},"Par exemple quand on développe une librairie (en C\u002FC++, voir un module NodeJS, ...), on définit une liste de méthodes\nque l'on rend publique et qui sont utilisées pour appeler cette librairie. Ces méthodes sont alors utilisées par des\nclients. L'API c'est ce contrat entre la librairie et le client.",[13,6158,6159],{},"Dans le cadre d'un site internet, l'API est le contrat entre un site internet et le client qui l'appelle. Comme tout\ncontrat il faut que celui-ci soit clairement défini si on veut que ça se passe bien entre les clients et les services.",[13,6161,6162,6163,143],{},"Il est important pour une bonne API ",[141,6164,6165],{},"public",[48,6167,6168,6171,6174,6177,6180],{},[51,6169,6170],{},"d'être stable dans le temps, ceci afin qu'un client qui utilise une API puisse continuer à l'utiliser sans devoir\ntout réécrire entre chaque version,",[51,6172,6173],{},"d'être fiable, cela va sans dire mais une API qui retourne des erreurs une fois sur deux ou qui ne fait pas ce qu'on\nlui demande va vite être abandonnée,",[51,6175,6176],{},"d'être simple d'utilisation et bien documentée, afin que le développeur passe le moins de temps possible à apprendre à\nl'utiliser,",[51,6178,6179],{},"d'avoir une bonne gestion des erreurs,",[51,6181,6182],{},"d'être performante.",[13,6184,6185],{},"Dans le monde du web, il existe plusieurs protocole\u002Fnorme afin de faciliter l'écriture de ce contrat d'interface, on\npeut citer par exemple:",[48,6187,6188,6191,6194,6197,6200],{},[51,6189,6190],{},"SOAP (Simple Object Access Protocol)",[51,6192,6193],{},"REST (Representational State Transfer)",[51,6195,6196],{},"XML-RPC (XML Remote Procedure Call)",[51,6198,6199],{},"GraphQL (créé par Facebook)",[51,6201,6202],{},"Falcor (qui est un protocole basé sur une implémentation de référence ; créé par Netflix)",[13,6204,6205],{},"Il existe plein de manières d'écrire des API. On peut aussi en écrire à base de socket réseau. Dans cet article je ne\nvais pas détailler toutes les méthodes possibles mais je vais présenter uniquement une selection d'API Web (Rest et\nGraphQL).",[64,6207,6209],{"id":6208},"pourquoi-écrire-une-api-web","Pourquoi écrire une API Web",[13,6211,6212,6213,6220],{},"Pour commencer si votre application web est une SPA",[97,6214,6215],{},[23,6216,106],{"href":6217,"ariaDescribedBy":6218,"dataFootnoteRef":104,"id":6219},"#user-content-fn-spa",[103],"user-content-fnref-spa",", il sera nécessaire d'avoir un contrat d'interface entre le client (SPA)\net le serveur (sauf s'il n'y a pas de serveur...). Ce contrat est généralement implicite.",[13,6222,6223],{},"Même pour un site web statique, ou pour un site dont les pages HTML sont générées coté serveur, nous pouvons considérer\nque l'API (l'interface de programation exposée) de l'application sont ses pages HTML. En effet, il existe des web\nscrapper qui lisent le contenu HTML des pages afin d'en lire le contenu (voir même qui en expose une API plus compréhensible).",[13,6225,6226,6227,298],{},"Du coup, la question la plus importante est ",[72,6228,6229],{},"\"Faut-il que mon interface de programmation soit publique ou privée ? Faut-il qu'elle\nsoit simple d'utilisation\"",[13,6231,6232],{},"Pour un site, dont les pages sont générées côté serveur et dont le rendu est en HTML, l'API n'est ni simple\nd'utilisation, ni documentable. Pour une SPA (Single Page Application), l'écriture d'une API utilisable par un\nprogramme est la norme et elle sera alors consumée par Angular, Vue, React, jQuery ....",[13,6234,6235],{},"Souhaite-t-on alors que notre application soit utilisable publiquement ?",[13,6237,6238],{},"Souhaite-t-on que d'autres applications développées par des tiers puisse utiliser notre API, librement ? gratuitement ?",[13,6240,6241],{},"Si oui, dois-je mettre en place une politique sur le nombre de requêtes maximales par utilisateur (OUI) ? Et de combien ?\nVais-je pouvoir supporter la charge ?",[13,6243,6244],{},"Prenez en considération dans votre décision que de tout facon, les gens feront ce qu'ils veulent. Après tout, si un\nhumain peut visualiser la page, un programme le peu également (WebScrapper).",[13,6246,6247],{},"communique avec le serveur via ses API. C'est en quelque sorte une application lourde mais écrite en JavaScript. Au démarrage du\nsite l'interface (le client) est chargée en mémoire (par morceau si on prend le lazy loading) et l'interface est ensuite générée à la\nvolée. Les pages HTML sont donc générées côté client et non coté serveur.",[64,6249,6251],{"id":6250},"quest-quune-bonne-documentation","Qu'est qu'une bonne documentation",[13,6253,6254],{},"Si on veux que l'API soit utilisée, il faut qu'elle soit bien documentée et surtout à jour. Sans cela personne ne voudra\nl'utiliser.",[13,6256,6257],{},"Pour faciliter l'écriture de la documentation certains outils permettent de générer celle-ci à partir du code (des\ntypes de données, du nom des méthodes, d'annotation ...), ce qui permet de maintenir plus facilement sa documentation avec\nl'évolution du code.\nPar contre, l'utilisation de générateur ne dédouanne pas de l'écriture de la partie qui ne peux pas être documentée (comme\nles descriptions, les exemples, ...).",[13,6259,6260],{},[123,6261],{"alt":5974,"src":6262},"\u002FProgrammation\u002Fcreation-api\u002Fdocumentation-swagger-head.png",[13,6264,6265],{},"La documentation doit contenir :",[48,6267,6268,6271,6274,6277,6280,6283],{},[51,6269,6270],{},"des exemples d'appels",[51,6272,6273],{},"des descriptions détaillées de ce que fait chaque point d'entrée,",[51,6275,6276],{},"quels type de données sont présentes en entrée et comment les utiliser,",[51,6278,6279],{},"mais aussi le type des données que l'on a en sortie,",[51,6281,6282],{},"les erreurs que peut retourner le endpoint et dans quelles circonstances,",[51,6284,6285],{},"comment seront traitées les données en entrée (batch, immédiat).",[13,6287,6288],{},"Au début de la documentation, ne pas oublier de documenter:",[48,6290,6291,6294],{},[51,6292,6293],{},"le fonctionnement de l'authentification",[51,6295,6296,6297],{},"les règles d'utilisation\n",[48,6298,6299,6302,6305,6314],{},[51,6300,6301],{},"ce qu'on est autorisé à faire",[51,6303,6304],{},"ce qu'on n'est pas autorisé à faire",[51,6306,6307,6308,6313],{},"le nombre d'appel par seconde\u002Fminutes\u002Fheures\u002F...et quels sont les headers HTTP permettant de récuperer le résultat\n(par exemple dans l'",[23,6309,6312],{"href":6310,"rel":6311},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Frest\u002Foverview\u002Fresources-in-the-rest-api#rate-limiting",[27],"API Github",",\non peut retrouver les headers: X-RateLimit-Limit, X-RateLimit-Remaining)",[51,6315,6316],{},"le fonctionnement de la pagination dans l'API",[13,6318,6319,6320,6323],{},"Il peut être efficace d'avoir une page au démarrage de l'API (",[72,6321,6322],{},"Quick Start",") donnant un première exemple complet d'un\npremier appel à l'API.",[13,6325,6326],{},"Dans le développement de l'API, l'écriture de la documentation est aussi important que l'écriture du code.",[64,6328,6330],{"id":6329},"gestion-des-erreurs","Gestion des erreurs",[13,6332,6333],{},"Pour la gestion des erreurs, l'API doit retourner le maximum d'informations pour que le développeur puisse comprendre\nl'erreur et effectuer une correction mais également suffisament d'informations pour que le développeur puisse les utiliser\ndans son programme pour retourner les problèmes fonctionnels à l'utilisateur final.",[13,6335,6336],{},"Par exemple dans une API Rest, il est important que les différents cas d'erreur soit explicités:",[48,6338,6339,6342,6349,6352],{},[51,6340,6341],{},"400 - BadRequest: The request is malformed.",[51,6343,6344,6345,6348],{},"404 - NotFound: The resource ",[72,6346,6347],{},"backup"," can't be found",[51,6350,6351],{},"401 - Unauthorized: The user is not authentified.",[51,6353,6354,6355,298],{},"403 - Forbidden: The user is not authorized to access to the resource ",[72,6356,6347],{},[13,6358,6359],{},"Ceci afin d'aider les développeurs à traiter tout les cas d'erreurs et d'afficher un message cohérent à l'utilisateur.",[13,6361,6362],{},"Lors d'une erreur, le retour de la requête doit toujours être le même. Voici un exemple de requête:",[145,6364,6366],{"className":4288,"code":6365,"language":4290,"meta":104,"style":104},"GET http:\u002F\u002F192.168.101.205:3000\u002Fapi\u002Fhosts\u002Funknownhost\n\n{\n  \"statusCode\": 404,\n  \"message\": \"Can't find configuration for the host with name unknownhost\",\n  \"error\": \"Not Found\",\n  \"errorCode\": \"HOST_NOT_FOUND\",\n  \"params\": {\n      \"host\": \"unknownhost\"\n  }\n}\n",[151,6367,6368,6376,6380,6384,6396,6408,6420,6432,6439,6449,6453],{"__ignoreMap":104},[154,6369,6370,6373],{"class":156,"line":157},[154,6371,6372],{"class":174},"GET http:",[154,6374,6375],{"class":647},"\u002F\u002F192.168.101.205:3000\u002Fapi\u002Fhosts\u002Funknownhost\n",[154,6377,6378],{"class":156,"line":178},[154,6379,212],{"emptyLinePlaceholder":211},[154,6381,6382],{"class":156,"line":208},[154,6383,4306],{"class":174},[154,6385,6386,6389,6391,6394],{"class":156,"line":215},[154,6387,6388],{"class":193},"  \"statusCode\"",[154,6390,4314],{"class":174},[154,6392,6393],{"class":228},"404",[154,6395,1484],{"class":174},[154,6397,6398,6401,6403,6406],{"class":156,"line":234},[154,6399,6400],{"class":193},"  \"message\"",[154,6402,4314],{"class":174},[154,6404,6405],{"class":201},"\"Can't find configuration for the host with name unknownhost\"",[154,6407,1484],{"class":174},[154,6409,6410,6413,6415,6418],{"class":156,"line":256},[154,6411,6412],{"class":193},"  \"error\"",[154,6414,4314],{"class":174},[154,6416,6417],{"class":201},"\"Not Found\"",[154,6419,1484],{"class":174},[154,6421,6422,6425,6427,6430],{"class":156,"line":374},[154,6423,6424],{"class":193},"  \"errorCode\"",[154,6426,4314],{"class":174},[154,6428,6429],{"class":201},"\"HOST_NOT_FOUND\"",[154,6431,1484],{"class":174},[154,6433,6434,6437],{"class":156,"line":402},[154,6435,6436],{"class":193},"  \"params\"",[154,6438,5097],{"class":174},[154,6440,6441,6444,6446],{"class":156,"line":427},[154,6442,6443],{"class":193},"      \"host\"",[154,6445,4314],{"class":174},[154,6447,6448],{"class":201},"\"unknownhost\"\n",[154,6450,6451],{"class":156,"line":450},[154,6452,5112],{"class":174},[154,6454,6455],{"class":156,"line":458},[154,6456,4329],{"class":174},[13,6458,6459,6460,6463,6464,6467],{},"Dans l'exemple ci-dessus, ",[151,6461,6462],{},"errorCode"," peut être utilisé pour indiquer à l'utilisateur, un champ obligatoire, (avec dans ",[151,6465,6466],{},"params"," le\nnom du champ) ou une règle de gestion mal utilisée.\nCe code erreur pourra alors être remontée à l'utilisateur final directement pour rendre l'interface plus réactive.",[64,6469,6471],{"id":6470},"cohérence","Cohérence",[13,6473,6474],{},"Pour faire une bonne documentation il faut également de bonnes bases. Pour cela il faut que l'API soit cohérente dans\nson fonctionnement. La cohérence est importante tant au niveau des paramètres que dans le contenu de la requête ou de la\nréponse.",[13,6476,6477,6478,6481,6482,4566,6485,143],{},"Par exemple, sur une API REST, imaginons que pour gérer la pagination un ",[72,6479,6480],{},"endpoint"," demande les paramètres ",[151,6483,6484],{},"skip",[151,6486,6487],{},"limit",[145,6489,6491],{"className":4288,"code":6490,"language":4290,"meta":104,"style":104},"GET \u002Fhosts\u002Fpc-ulrich\u002Fbackups?skip=5&limit=10\n\n{\n    \"skip\": 5,\n    \"limit\": 10,\n    \"size\": 100,\n    \"result\": [\n        {\n            \"id\": 1\n        },\n        {\n            \"id\": 2\n        }\n    ]\n}\n",[151,6492,6493,6507,6511,6515,6526,6538,6550,6557,6562,6572,6577,6581,6590,6594,6599],{"__ignoreMap":104},[154,6494,6495,6498,6501,6504],{"class":156,"line":157},[154,6496,6497],{"class":174},"GET \u002Fhosts\u002Fpc-ulrich\u002Fbackups?skip=",[154,6499,6500],{"class":228},"5",[154,6502,6503],{"class":174},"&limit=",[154,6505,6506],{"class":228},"10\n",[154,6508,6509],{"class":156,"line":178},[154,6510,212],{"emptyLinePlaceholder":211},[154,6512,6513],{"class":156,"line":208},[154,6514,4306],{"class":174},[154,6516,6517,6520,6522,6524],{"class":156,"line":215},[154,6518,6519],{"class":193},"    \"skip\"",[154,6521,4314],{"class":174},[154,6523,6500],{"class":228},[154,6525,1484],{"class":174},[154,6527,6528,6531,6533,6536],{"class":156,"line":234},[154,6529,6530],{"class":193},"    \"limit\"",[154,6532,4314],{"class":174},[154,6534,6535],{"class":228},"10",[154,6537,1484],{"class":174},[154,6539,6540,6543,6545,6548],{"class":156,"line":256},[154,6541,6542],{"class":193},"    \"size\"",[154,6544,4314],{"class":174},[154,6546,6547],{"class":228},"100",[154,6549,1484],{"class":174},[154,6551,6552,6555],{"class":156,"line":374},[154,6553,6554],{"class":193},"    \"result\"",[154,6556,5643],{"class":174},[154,6558,6559],{"class":156,"line":402},[154,6560,6561],{"class":174},"        {\n",[154,6563,6564,6567,6569],{"class":156,"line":427},[154,6565,6566],{"class":193},"            \"id\"",[154,6568,4314],{"class":174},[154,6570,6571],{"class":228},"1\n",[154,6573,6574],{"class":156,"line":450},[154,6575,6576],{"class":174},"        },\n",[154,6578,6579],{"class":156,"line":458},[154,6580,6561],{"class":174},[154,6582,6583,6585,6587],{"class":156,"line":463},[154,6584,6566],{"class":193},[154,6586,4314],{"class":174},[154,6588,6589],{"class":228},"2\n",[154,6591,6592],{"class":156,"line":485},[154,6593,566],{"class":174},[154,6595,6596],{"class":156,"line":499},[154,6597,6598],{"class":174},"    ]\n",[154,6600,6601],{"class":156,"line":504},[154,6602,4329],{"class":174},[13,6604,6605,6606,6608],{},"N'utilisez pas pour un autre ",[72,6607,6480],{}," de votre application des paramètres différents. De même, n'utilisez pas dans\nle résultat de la requête des noms différents de ceux utilisés dans les paramètres.",[13,6610,6611],{},"Enfin structurez le contenu de la réponse toujours de la même manière, pour que vos utilisateurs puissent développer\ndes méthodes génériques lors de l'utilisation de votre API (Par exemple une méthode de pagination générique).",[145,6613,6615],{"className":4288,"code":6614,"language":4290,"meta":104,"style":104},"GET \u002Fhosts?start=5&size=10\n\n{\n    \"debut\": 5,\n    \"taille\": 10,\n    \"fin\": 100,\n    \"liste\": [\n        {\n            \"id\": 1\n        },\n        {\n            \"id\": 2\n        }\n    ]\n}\n",[151,6616,6617,6629,6633,6637,6648,6659,6670,6677,6681,6689,6693,6697,6705,6709,6713],{"__ignoreMap":104},[154,6618,6619,6622,6624,6627],{"class":156,"line":157},[154,6620,6621],{"class":174},"GET \u002Fhosts?start=",[154,6623,6500],{"class":228},[154,6625,6626],{"class":174},"&size=",[154,6628,6506],{"class":228},[154,6630,6631],{"class":156,"line":178},[154,6632,212],{"emptyLinePlaceholder":211},[154,6634,6635],{"class":156,"line":208},[154,6636,4306],{"class":174},[154,6638,6639,6642,6644,6646],{"class":156,"line":215},[154,6640,6641],{"class":193},"    \"debut\"",[154,6643,4314],{"class":174},[154,6645,6500],{"class":228},[154,6647,1484],{"class":174},[154,6649,6650,6653,6655,6657],{"class":156,"line":234},[154,6651,6652],{"class":193},"    \"taille\"",[154,6654,4314],{"class":174},[154,6656,6535],{"class":228},[154,6658,1484],{"class":174},[154,6660,6661,6664,6666,6668],{"class":156,"line":256},[154,6662,6663],{"class":193},"    \"fin\"",[154,6665,4314],{"class":174},[154,6667,6547],{"class":228},[154,6669,1484],{"class":174},[154,6671,6672,6675],{"class":156,"line":374},[154,6673,6674],{"class":193},"    \"liste\"",[154,6676,5643],{"class":174},[154,6678,6679],{"class":156,"line":402},[154,6680,6561],{"class":174},[154,6682,6683,6685,6687],{"class":156,"line":427},[154,6684,6566],{"class":193},[154,6686,4314],{"class":174},[154,6688,6571],{"class":228},[154,6690,6691],{"class":156,"line":450},[154,6692,6576],{"class":174},[154,6694,6695],{"class":156,"line":458},[154,6696,6561],{"class":174},[154,6698,6699,6701,6703],{"class":156,"line":463},[154,6700,6566],{"class":193},[154,6702,4314],{"class":174},[154,6704,6589],{"class":228},[154,6706,6707],{"class":156,"line":485},[154,6708,566],{"class":174},[154,6710,6711],{"class":156,"line":499},[154,6712,6598],{"class":174},[154,6714,6715],{"class":156,"line":504},[154,6716,4329],{"class":174},[13,6718,6719],{},"Cela aurait comme conséquence de perdre les utilisateurs (développeurs) qui utiliseront votre API.",[13,6721,6722,6723,6725],{},"Les structures, les champs utilisés sur les différents ",[72,6724,6480],{}," de votre API doivent toujours suivre la même\nlogique et par exemple:",[48,6727,6728,6734,6845,6848],{},[51,6729,6730,6731,1249],{},"nommer toujours les champs ayant la même fonction de la même manière: toujours nommer la date de création ",[151,6732,6733],{},"createdAt",[51,6735,6736,6737,6740,6797,6799,6800],{},"toujours retourner la resource demandé directement ",[72,6738,6739],{},"ou inversement toujours encapsuler la resource demandée dans un\nsous-objet",[145,6741,6743],{"className":4288,"code":6742,"language":4290,"meta":104,"style":104},"{\n  \"result\": [\n    {\n      \"id\": 1\n    },\n    {\n      \"id\": 2\n    }\n  ]\n}\n",[151,6744,6745,6749,6756,6760,6768,6773,6777,6785,6789,6793],{"__ignoreMap":104},[154,6746,6747],{"class":156,"line":157},[154,6748,4306],{"class":174},[154,6750,6751,6754],{"class":156,"line":178},[154,6752,6753],{"class":193},"  \"result\"",[154,6755,5643],{"class":174},[154,6757,6758],{"class":156,"line":208},[154,6759,5648],{"class":174},[154,6761,6762,6764,6766],{"class":156,"line":215},[154,6763,5665],{"class":193},[154,6765,4314],{"class":174},[154,6767,6571],{"class":228},[154,6769,6770],{"class":156,"line":234},[154,6771,6772],{"class":174},"    },\n",[154,6774,6775],{"class":156,"line":256},[154,6776,5648],{"class":174},[154,6778,6779,6781,6783],{"class":156,"line":374},[154,6780,5665],{"class":193},[154,6782,4314],{"class":174},[154,6784,6589],{"class":228},[154,6786,6787],{"class":156,"line":402},[154,6788,367],{"class":174},[154,6790,6791],{"class":156,"line":427},[154,6792,5923],{"class":174},[154,6794,6795],{"class":156,"line":450},[154,6796,4329],{"class":174},[5964,6798],{},"vs",[145,6801,6803],{"className":4288,"code":6802,"language":4290,"meta":104,"style":104},"[\n  {\n    \"id\": 1\n  },\n  {\n    \"id\": 2\n  }\n]\n",[151,6804,6805,6809,6813,6821,6825,6829,6837,6841],{"__ignoreMap":104},[154,6806,6807],{"class":156,"line":157},[154,6808,4353],{"class":174},[154,6810,6811],{"class":156,"line":178},[154,6812,4358],{"class":174},[154,6814,6815,6817,6819],{"class":156,"line":208},[154,6816,4363],{"class":193},[154,6818,4314],{"class":174},[154,6820,6571],{"class":228},[154,6822,6823],{"class":156,"line":215},[154,6824,4379],{"class":174},[154,6826,6827],{"class":156,"line":234},[154,6828,4358],{"class":174},[154,6830,6831,6833,6835],{"class":156,"line":256},[154,6832,4363],{"class":193},[154,6834,4314],{"class":174},[154,6836,6589],{"class":228},[154,6838,6839],{"class":156,"line":374},[154,6840,5112],{"class":174},[154,6842,6843],{"class":156,"line":402},[154,6844,4406],{"class":174},[51,6846,6847],{},"toujours gérer la pagination de la même manière dans la réponse (par des entête http, ou directement dans le body,\nmais surtout avec les mêmes noms de champs).",[51,6849,6850],{},"toujours gérer les erreurs de la même manière.",[64,6852,6854],{"id":6853},"gérer-le-versionning-de-lapi","Gérer le versionning de l'API",[13,6856,6857],{},"Si vous souhaitez changer le fonctionnement de l'API, surtout ne cassez pas la cohérence de l'API, ni la version\nutilisée par tous. Pour cela vous avez plusieurs choix, et ils sont complémentaires:",[48,6859,6860,6880,6896],{},[51,6861,6862,6863,6865,6866,6869,6870,6873,6874,6876,6877,6879],{},"Vous pouvez créer une nouvelle version de l'API.",[5964,6864],{},"En créant une nouvelle version de l'API (passage de ",[151,6867,6868],{},"\u002Fapi\u002Fv1\u002F..."," à ",[151,6871,6872],{},"\u002Fapi\u002Fv2\u002F...","), vous vous assurez que l'ancienne\nversion fonctionne toujours et que les clients pourront migrer doucement de l'ancienne version vers la nouvelle.",[5964,6875],{},"Si vous souhaitez casser la cohérence de l'API (par exemple remonter les infos skip et limit du body dans des headers\nX-Pagination-Skip et X-Pagination-Limit), vous pouvez le faire une à une sur chaque API.",[5964,6878],{},"Pensez à un plan de décommissionnement pour ne pas maintenir 50 versions d'API différentes. Prévenez vos utilisateurs\nque les anciennes versions vont être décommissionées avec une date raisonable pour qu'ils puissent modifier leurs\napplications.",[51,6881,6882,6883,6886,6887,6889,6890,6892,6893,6895],{},"Lors de la création d'une nouvelle API, il peut être intéressant de ",[72,6884,6885],{},"déprécier"," les attributs de l'ancienne API, cela\nvous permet de prévenir l'utilisateur qu'il utilise un champ déprécié et qu'il sera décommisionné.",[5964,6888],{},"L'ajout d'attribut ne pose généralement pas de problème, mais cela peut vous permettre de supprimer des attributs\naprès avoir veillé à prévenir vos utilisateurs de leur obsolescence future sans forcément créer une nouvelle API.",[5964,6891],{},"Par contre cela nécessite de faire des modifications peu structurantes.",[5964,6894],{},"Pour des modifications plus structurantes vous devrez alors créer des nouveaux endpoint au coeur de l'API au risque\nde perdre l'utilisateur.",[51,6897,6898,6899,6901,6902,6904,6905,6907],{},"Monitorer votre API et son utilisation (analytics).",[5964,6900],{},"Cela vous permettra de savoir si vos API sont utilisées, leurs taux d'utilisation.",[5964,6903],{},"Etudier le taux d'utilisation vous permet ainsi de savoir le risque à décommissionner une API, mais aussi peut vous\nmotiver à améliorer les API les plus utilisées.",[5964,6906],{},"Avec certains framework (GraphQL) il est même possible de monitorer l'utilisation des attributs de votre API. Cela\nvous permet de plus facilement décommissionner les attributs de votre API au fur et à mesure de leur dépréciation.",[64,6909,6911],{"id":6910},"pour-le-client","Pour le client",[13,6913,6914],{},"Une bonne API doit être écrite pour le client qui va l'utiliser.",[13,6916,6917],{},"Il est important de ne pas créer son API en se basant sur son modèle interne mais en créant son API sur son usage.",[13,6919,6920],{},"Un appel d'API peut nécessiter plusieurs appels internes, et inversement plusieurs API peuvent faire appel aux mêmes\ndonnées internes. C'est au serveur derrière l'API ensuite de gérer un système de cache (avec invalidation de celui-ci)\npour limiter les appels à son back, ou sa base de données trop régulièrement.",[13,6922,6923],{},"De la même manière certains champs internes peuvent nécessiter d'être transformés pour être intégrés ou pour être exposés.",[13,6925,6926],{},"Certaines données internes n'ont parfois même pas besoin d'être exposées.",[13,6928,6929],{},"Du coup il faut se poser les bonnes questions lors de la création de son API :",[48,6931,6932,6935,6938],{},[51,6933,6934],{},"Qui sont les clients qui vont appeler mon API ?",[51,6936,6937],{},"Quelles cinématiques utilisateurs se cachent derrière ses clients ?",[51,6939,6940,6941],{},"Lors de chaque appel de quelles données auront besoin les clients ?\n",[48,6942,6943,6946,6949],{},[51,6944,6945],{},"Dois-je regrouper certains champs dans une même resource pour limiter le nombre d'appels ?",[51,6947,6948],{},"Dois-je déplacer certaines données dans des sous-resources pour limiter la quantité de données lors d'un appel ?",[51,6950,6951],{},"Dois-je ajouter de la pagination, de la projection, de la recherche pour limiter la quantité de données qui\nressortira de mon API ?",[13,6953,6954],{},"Il faut prendre en compte qu'en fonction de l'appelant le résultat peut être différent. Une application mobile ne\nprésentera pas forcément les données de la même manière qu'un site WEB. Est-ce que l'API doit savoir répondre aux deux ?\n(Certains framework savent mieux répondre à ce genre de questions que d'autres).",[64,6956,6958],{"id":6957},"conclusion","Conclusion",[13,6960,6961],{},"Si vous avez aimé cet article, je vous invite à lire la suite qui paraitera bientôt.",[4118,6963,6965,6968],{"className":6964,"dataFootnotes":104},[4121],[64,6966,4126],{"className":6967,"id":103},[4125],[4128,6969,6970],{},[51,6971,6973,6974],{"id":6972},"user-content-fn-spa","une SPA (Single Page Application) est une application dont l'interface Web est entièrement écrite en javascript et qui ",[23,6975,4140],{"href":6976,"ariaLabel":4137,"className":6977,"dataFootnoteBackref":104},"#user-content-fnref-spa",[4139],[4151,6979,6980],{},"html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sV9Aq, html code.shiki .sV9Aq{--shiki-default:#7F848E;--shiki-default-font-style:italic}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":104,"searchDepth":178,"depth":178,"links":6982},[6983,6984,6985,6986,6987,6988,6989,6990],{"id":6208,"depth":178,"text":6209},{"id":6250,"depth":178,"text":6251},{"id":6329,"depth":178,"text":6330},{"id":6470,"depth":178,"text":6471},{"id":6853,"depth":178,"text":6854},{"id":6910,"depth":178,"text":6911},{"id":6957,"depth":178,"text":6958},{"id":103,"depth":178,"text":4126},"2020-10-11",{"type":10,"value":6993},[6994,6996,6998,7012,7014],[13,6995,4216],{},[13,6997,6133],{},[48,6999,7000,7004,7008],{},[51,7001,7002],{},[23,7003,4227],{"href":4226},[51,7005,7006],{},[23,7007,4233],{"href":4232},[51,7009,7010],{},[23,7011,4239],{"href":4238},[13,7013,6150],{},[13,7015,6153],{},{"planet":211},"\u002Fpost\u002Fcreation-api-1",{"title":6126,"description":4216},"creation-api-1","posts\u002FProgrammation\u002F2020-10-11_creation-api-1",[6118,6119,6120,6121,6122],"TBg-AMlBnBvdkbTX1a-CGytRJUL2yk4FcVzMHR6lj6c",{"id":7024,"title":7025,"author":8,"body":7026,"category":7645,"categorySlug":7770,"date":7771,"description":7030,"excerpt":7772,"extension":4199,"location":4200,"meta":7801,"navigation":211,"path":7802,"published":211,"seo":7803,"slug":7770,"stem":7804,"tags":7805,"timeToRead":463,"__hash__":7808},"posts\u002Fposts\u002FWoodstock\u002F2020-09-20_woodstock.md","Woodstock Backup v1.0.0",{"type":10,"value":7027,"toc":7755},[7028,7031,7042,7047,7051,7062,7071,7074,7077,7082,7090,7095,7101,7139,7142,7146,7149,7175,7178,7184,7188,7195,7202,7205,7208,7211,7214,7220,7223,7234,7237,7240,7244,7251,7257,7260,7263,7268,7276,7279,7285,7289,7296,7301,7304,7313,7316,7319,7322,7325,7329,7332,7384,7388,7402,7406,7413,7416,7430,7439,7442,7453,7457,7460,7482,7485,7489,7534,7537,7541,7549,7558,7561,7629,7632,7726,7729,7732,7735],[13,7029,7030],{},"Bonjour à tous,",[13,7032,7033,7034,7038,7039,298],{},"Un projet ",[23,7035,7037],{"href":7036},"\u002Fpost\u002F2020-07-27-fin-passprotect\u002F","s'en va"," et un autre commence.\nJe suis heureux de vous présenter ce nouveau projet: ",[141,7040,7041],{},"Woodstock Backup",[7043,7044,7046],"h1",{"id":7045},"genèse-du-projet","Genèse du projet",[64,7048,7050],{"id":7049},"mes-problèmes","Mes problèmes",[13,7052,7053,7054,7061],{},"Pour faire des sauvegardes, j'utilisais jusqu'ici ",[141,7055,7056],{},[23,7057,7060],{"href":7058,"rel":7059},"https:\u002F\u002Fbackuppc.github.io\u002Fbackuppc\u002F",[27],"BackupPC",". C'est un très bon logiciel pour effectuer des sauvegardes de plusieurs machines sur une instance centralisée.",[13,7063,7064,7066,7067,7070],{},[141,7065,7060],{}," est écrit en ",[72,7068,7069],{},"Perl"," avec des partie en C. En effet il se base sur un fork de rsync qui permet d'enregistrer le résultat des sauvegardes dans un format qui lui est propre. Malgré qu'il fonctionne très bien j'ai eu plusieurs problèmes avec récemment.",[13,7072,7073],{},"Il permet de faire des sauvegardes lancées depuis un serveur centralisé. Ce dernier vérifie régulièrement si les différents PC à sauvegarder sont présents sur le réseau et s'y connecte pour faire les sauvegardes sur la base d'un calendrier.",[13,7075,7076],{},"Le serveur a besoin de pouvoir se connecter sur tous les clients (incovénient que je suis prêt à accepter) mais les clients n'ont du coup pas besoin de devoir se connecter au serveur (avec le risque qu'un client compromis, compromette le serveur).",[13,7078,7079,7081],{},[141,7080,7060],{}," est également capable de décider de l'heure de la sauvegarde en fonction de la date de la dernière sauvegarde ainsi que de la présence du PC sur le réseau. Ainsi si un PC est toujours présent, il peut être sauvegardé tout en préservant les heures où la personne a besoin de toute la puissance de son PC, sauf si la sauvegarde date de trop.",[13,7083,7084,7085,7089],{},"Je vous invite fortement à aller à l'adresse ",[23,7086,7058],{"href":7087,"rel":7088},"https:\u002F\u002Fbackuppc.github.io\u002Fbackuppc\u002Finfo.html",[27]," pour en savoir plus sur BackupPC.",[13,7091,7092],{},[123,7093],{"alt":7060,"src":7094},"\u002FWoodstock\u002Fbackuppc.png",[13,7096,7097,7098,7100],{},"Voici les quelques problèmes que j'ai eu récemment avec les dernières versions de ",[141,7099,7060],{}," :",[48,7102,7103,7112],{},[51,7104,7105,7106,7108,7109,7111],{},"Lors du passage de la version v3 à la version v4, et alors que le pool de sauvegarde était stocké sur un partage NFS, je me suis mis à perdre des fichiers: Impossible de restaurer des fichiers.",[5964,7107],{},"Après quelques recherches j'ai découvert que BackupPC v4 n'était pas compatible avec NFS, et j'ai dû basculer toute mon installation sur un disque iSCSI sur mon NAS.",[5964,7110],{},"J'ai alors perdu mon historique de sauvegarde (je n'avais alors pas assez de place pour faire faire mes sauvegardes dans un coin et des expérimentations\u002Fmigrations de l'autre).",[51,7113,7114,7115,7117,7119,7120,7122,7123,7125,7126,7128,7129,7131,7132,7134,298],{},"Une fois les sauvegardes effectuées je souhaite pouvoir copier le résultat sur un disque dur USB (qui contient donc alors la dernière version). Cette archive est alors déposée régulièrement hors site afin de minimiser le risque de perte de données.",[5964,7116],{},[141,7118,7060],{}," permet de le faire nativement en exportant des archives au format TAR sur ce disque dur USB (moyennant une pré-étape pour monter le disque, et une post-étape pour le démonter).",[5964,7121],{},"Cela me pose problème, en effet je veux pouvoir facilement accéder au contenu de l'archive (donc sans ouvrir le tar), et cette dernière est également très longue à créer.\nDe plus il m'est déjà arrivé d'ouvrir un tar et de me rendre compte que l'archive était corrompue (tronquée) soit par manque de place soit car l'archivage n'avait pas fonctionné correctement mais sans avertissement.",[5964,7124],{},"Je m'emploie donc à créer un script qui à l'aide d'un connecteur Fuse BackupPC me permet de synchroniser les dernières versions des sauvegardes avec rsync vers le disque dur USB.",[5964,7127],{},"Malheureusement le script FUSE n'est pas très maintenu, et pose problème avec des fichiers de plusieurs centaines de gigaoctets, ou avec les permissions de fichiers sauvegardés avec des machines Windows. J'ai donc adapté le script Fuse à mes problèmes pour régler uniquement le problème de droits.",[5964,7130],{},"La modification n'est pas super propre: je squeeze les droits si l'utilisateur à un uid particulier.",[5964,7133],{},[23,7135,7138],{"href":7136,"rel":7137},"https:\u002F\u002Fgist.github.com\u002Fphoenix741\u002F99a5076569b01ba5a116cec24a798d5f",[27],"Vous pouvez retrouver ma modification ici",[13,7140,7141],{},"Le fait est que même si la solution continue de tourner actuellement et fonctionne bien, j'ai perdu une partie de la confiance que j'ai en ce produit.",[64,7143,7145],{"id":7144},"etudes-des-autres-solutions","Etudes des autres solutions",[13,7147,7148],{},"Quelques critères:",[48,7150,7151,7157,7160,7163,7166,7169,7172],{},[51,7152,7153,7154,298],{},"Déjà un premier critère est que la solution doit être ",[72,7155,7156],{},"Open Source",[51,7158,7159],{},"Je ne veux pas de client lourd pour visualiser mes sauvegardes (seul un client léger pour y accéder de n'importe où).",[51,7161,7162],{},"Je veux pouvoir facilement créer des archives sur des disques durs USB à plat (sans format spécial comme zip, tar, ...)",[51,7164,7165],{},"Je dois pouvoir sauvegarder des machines sous Windows ou sous Linux.",[51,7167,7168],{},"Je suis prêt à installer un client lourd sur le client qui est sauvegardé.",[51,7170,7171],{},"Je dois sauvegarder 6 machines (1 serveur dédié, 2 Vieux PC portable, 2 Vieux PC Fixe, 1 Vieux NAS) sans que les utilisateurs (ma femme, moi) n'aient à y penser.",[51,7173,7174],{},"Si le logiciel est facilement contrôlable par API et via une IHM simple d'utilisation, c'est un grand plus.",[13,7176,7177],{},"J'ai regardé plusieurs solutions et voici les deux solutions qui m'ont tapé dans l'oeil (et ça fait mal) en plus de BackupPC que j'utilse déjà.",[13,7179,7180,7181,7183],{},"Je précise que mes tests sont alors limités car je n'ai pas assez d'espace de stockage pour avoir à la fois le pool de ",[141,7182,7060],{}," de environ 2To et le pool d'un autre gestionnaire de sauvegarde en même temps.",[133,7185,7187],{"id":7186},"urbackup","UrBackup",[13,7189,7190,7191,298],{},"J'ai alors installé ",[23,7192,7187],{"href":7193,"rel":7194},"https:\u002F\u002Fwww.urbackup.org\u002F",[27],[13,7196,7197,7198,7201],{},"J'ai apprécié l'utiliser en utilisant le stockage ",[72,7199,7200],{},"btrfs",". Il permet aussi d'utiliser un système de stockage qui lui est propre.",[13,7203,7204],{},"Je n'ai pas trouvé de méthode pour effectuer de l'archivage sur disque dur USB des dernières sauvegardes uniquement (et pas l'ensemble du pool) pour stockage off-site. Ce point pourrait être resolvable en utilisant justement le stockage Btrfs et en écrivant mes propres scripts.",[13,7206,7207],{},"Sous Windows, il permet aussi de créer des Snapshots (équivalent LVM) pour permettre de faire des sauvegardes de fichier en lecture.",[13,7209,7210],{},"Il aussi capable de faire des images disque des machines Windows.",[13,7212,7213],{},"UrBackup utilise un client à installer sur chaque ordinateur à sauvegarder. Cela ne pose pas de problème et peut permettre d'optimiser la vitesse de sauvegarde par rapport à un simple rsync, et d'avoir un format de stockage propre pour optimiser la taille occupé.",[13,7215,7216,7217,7219],{},"Bref, sur le papier ",[141,7218,7187],{}," a tout pour plaire.",[13,7221,7222],{},"Pourquoi ne pas l'utiliser ? Bonne question :).",[13,7224,7225,7226,7229,7230,7233],{},"La migration de mon historique de sauvegarde de BackupPC vers UrBackup n'était pas possible facilement. Il aurait fallu pour cela qu'en plus de générer la structure ",[72,7227,7228],{},"Btrfs"," qui va bien créer la structure dans la base ",[72,7231,7232],{},"Sqlite",", ce que je n'ai pas forcément trouvé pratique.",[13,7235,7236],{},"Il me semble aussi que je ne trouvais pas l'interface très pratique (entre autres pour la restauration de fichiers).",[13,7238,7239],{},"Je n'ai pas plus de raisons que cela de ne pas l'utiliser, donc si vous cherchez un logiciel pour faire des sauvegardes n'hésitez pas à le tester.",[133,7241,7243],{"id":7242},"borg","Borg",[13,7245,7246,7247,298],{},"J'ai aimé utiliser également ",[23,7248,7243],{"href":7249,"rel":7250},"https:\u002F\u002Fborgbackup.readthedocs.io\u002F",[27],[13,7252,7253,7254,7256],{},"Surtout la possibilité de pouvoir chiffrer les sauvegardes, ainsi que les performances de ",[141,7255,7243],{}," mêmes.",[13,7258,7259],{},"Malheureusement, je souhaite pouvoir facilement déchiffrer le contenu depuis le serveur principal pour archiver plusieurs sauvegardes de plusieurs machines en même temps.",[13,7261,7262],{},"Je souhaite également pouvoir sauvegarder toute sortes d'ordinateurs. Certains appartenant au réseau local, d'autres se trouvant être des dédiés sur Internet. Des PC sous Linux mais aussi un PC sous Windows.",[13,7264,7265,7267],{},[141,7266,7243],{}," m'aurait alors posé quelques soucis sous Windows.",[13,7269,7270,7271],{},"Je ne souhaite pas que mes serveurs dédiés aient accès à mon réseau local pour faire les sauvegardes, ni aient accès aux serveurs de backup directement au risque de compromettre la sécurité. ",[23,7272,7275],{"href":7273,"rel":7274},"https:\u002F\u002Fborgbackup.readthedocs.io\u002Fen\u002Fstable\u002Ffaq.html#how-can-i-protect-against-a-hacked-backup-client",[27],"Il faut alors paramétrer le serveur de backup pour gérer cela au niveau de ssh",[13,7277,7278],{},"Il n'est pas possible non plus de mutualiser les sauvegardes dans un seul repo, ce qui au delà des problèmes de sécurité, pose également des problèmes de lock.",[13,7280,7281,7282,7284],{},"Je pense que ",[141,7283,7243],{}," est par contre un très bon logiciel pour faire une sauvegarde de son PC perso quand on ne s'occupe que de soi et qu'on a qu'un seul PC.",[133,7286,7288],{"id":7287},"autres-tests","Autres tests",[13,7290,7291,7292,7295],{},"J'ai également testé rapidement ",[141,7293,7294],{},"Burp"," pour lequel je jette un oeil également régulièrement, mais l'IHM non intégré était alors très lente lors de mes tests, et la ligne de commande me rendait la restauration de fichiers complexe lors du peu de fois que je l'ai utilisé.",[13,7297,7298,7299,298],{},"Il est alors difficile de trouver un concurrent qui me convienne pour remplacer ",[141,7300,7060],{},[13,7302,7303],{},"Si vous pensez que je me suis trompé sur les tests que j'ai fait ci-dessus, ou que vous voyez un autre logiciel de sauvegardes qui pourrait me convenir, n'hésitez pas à m'envoyer un mail.",[13,7305,7306,7307,298],{},"A l'heure actuelle j'ai décidé d'écrire mon propre système de sauvegarde que j'ai nommé ",[141,7308,7309],{},[23,7310,7041],{"href":7311,"rel":7312},"https:\u002F\u002Fwoodstock.shadoware.org\u002F",[27],[7043,7314,7041],{"id":7315},"woodstock-backup",[13,7317,7318],{},"A défaut de tourner en rond et de ne pas être complètement satisfait, j'ai décidé de développer ma propre solution. Ainsi si je n'ai pas toutes les fonctionnalités que je désire, je n'ai qu'à m'en prendre à moi-même et les développer.",[13,7320,7321],{},"Il n'est pas facile de choisir un nom pour un logiciel.",[13,7323,7324],{},"Lors du développement de cette application de sauvegarde, je regardais un épisode d'une série dont l'histoire se passait à Woodstock, d'où le nom :).",[64,7326,7328],{"id":7327},"les-fonctionnalités","Les fonctionnalités",[13,7330,7331],{},"Quels sont les fonctionnalités à l'heure actuelle :",[48,7333,7334,7337,7340,7343,7346,7356,7361,7373,7381],{},[51,7335,7336],{},"Faire des sauvegardes de façon régulière (par exemple une fois par jour) et lors de la dispo de l'ordinateur.",[51,7338,7339],{},"Pour chaque sauvegarde il est possible d'éxecuter des étapes en amont et en aval, voir de faire plusieurs sauvegardes de plusieurs dossiers.",[51,7341,7342],{},"On peut lister les sauvegardes pour chaque hôte.",[51,7344,7345],{},"On peut télécharger les fichiers un par un ou en téléchargeant un fichier Zip.",[51,7347,7348,7349,7351,7352,7355],{},"Les sauvegardes sont stockées sur un système de fichier ",[72,7350,7228],{}," ce qui permet de bénéficier du système de ",[72,7353,7354],{},"Snapshot"," pour la déduplication, et de pouvoir accéder (à des fins d'archivage) directement aux sauvegardes sans contrainte.",[51,7357,7358,7360],{},[72,7359,7228],{}," permet également de compresser les sauvegardes (avec les bonnes options au montage).",[51,7362,7363,7364,7367,7368,298],{},"Comme ",[72,7365,7366],{},"RSync"," est utilisé, il est installé sur tous les clients (même windows), ce qui facilite son utilisation",[97,7369,7370],{},[23,7371,106],{"href":101,"ariaDescribedBy":7372,"dataFootnoteRef":104,"id":105},[103],[51,7374,7375,7376],{},"L'application possède une interface moderne (à mon goût)",[97,7377,7378],{},[23,7379,1447],{"href":1444,"ariaDescribedBy":7380,"dataFootnoteRef":104,"id":1446},[103],[51,7382,7383],{},"L'application est contrôlable via une API Rest et via une API GraphQL.",[64,7385,7387],{"id":7386},"quelques-captures-décran","Quelques captures d'écran",[13,7389,7390,7394,7398],{},[123,7391],{"alt":7392,"src":7393},"Dashboard","\u002FWoodstock\u002Fdashboard.png",[123,7395],{"alt":7396,"src":7397},"Hosts","\u002FWoodstock\u002Fhosts.png",[123,7399],{"alt":7400,"src":7401},"RunningTask","\u002FWoodstock\u002Frunning_tasks_0.png",[64,7403,7405],{"id":7404},"comment-linstaller","Comment l'installer ?",[13,7407,7408,7409,7412],{},"J'ai écrit un site Internet pour présenter Woodstock: ",[23,7410,7311],{"href":7311,"rel":7411},[27]," et porter la documentation.",[13,7414,7415],{},"Vous pouvez retrouver les liens de téléchargement depuis le repository de code sources :",[48,7417,7418,7424],{},[51,7419,7420],{},[23,7421,7422],{"href":7422,"rel":7423},"https:\u002F\u002Fgogs.shadoware.org\u002FShadowareOrg\u002Fwoodstock-backup",[27],[51,7425,7426],{},[23,7427,7428],{"href":7428,"rel":7429},"https:\u002F\u002Fgithub.com\u002Fphoenix741\u002Fwoodstock-backup",[27],[13,7431,7432,7433,7438],{},"L'installation se fait très simplement, j'ai écrit un peu de ",[23,7434,7437],{"href":7435,"rel":7436},"https:\u002F\u002Fwoodstock.shadoware.org\u002Fdoc\u002Finstallation.html",[27],"documentation"," pour expliquer cela.",[13,7440,7441],{},"Il est possible d'effectuer une installation via:",[48,7443,7444,7447,7450],{},[51,7445,7446],{},"les sources",[51,7448,7449],{},"un paquet debian\u002Fubuntu",[51,7451,7452],{},"l'image docker (c'est l'installation que j'utilise moi-même)",[64,7454,7456],{"id":7455},"par-rapport-à-mon-besoin","Par rapport à mon besoin",[13,7458,7459],{},"Reprenons mes critères :",[48,7461,7462,7467,7470,7473,7476,7479],{},[51,7463,7464,7466],{},[72,7465,7156],{},": Check",[51,7468,7469],{},"Client léger: Check",[51,7471,7472],{},"Archivage sur disque dur USB à plat: Partiel (Btrfs me permet de le faire manuellement à l'aide d'un script maison)",[51,7474,7475],{},"Compatible Windows\u002FLinux: Check (via rsync)",[51,7477,7478],{},"Sauvegarde sans y penser (automatique): Check",[51,7480,7481],{},"API ou IHM simple d'utilisation: Milk Check. L'API est là. Il est simple d'utilisation pour moi, mais peut encore être amélioré",[13,7483,7484],{},"Donc pour l'instant il répond à mes besoins mais reste améliorable.",[64,7486,7488],{"id":7487},"roadmap","Roadmap",[48,7490,7491,7494,7497],{},[51,7492,7493],{},"Avoir un outil d'archivage automatique intégré.",[51,7495,7496],{},"Les suppressions automatiques de sauvegardes sont à prévoir également.",[51,7498,7499,7500],{},"Peut-être remplacer Btrfs par autre chose, car actuellement",[48,7501,7502,7505,7511,7514,7517],{},[51,7503,7504],{},"Sans Btrfs, le logiciel ne peut pas fonctionner, donc cela limite les OS du serveur de sauvegarde.",[51,7506,7507,7508,7510],{},"Il y aurait des problèmes de performances avec ",[72,7509,7228],{}," en cas de trop grand nombre de snapshots et de l'utilisation des qgroups. Pour l'instant je n'ai ce genre de problème que lors de la suppression de sauvegarde.",[51,7512,7513],{},"Il n'est pas possible de faire de la déduplication inter-machines simplement.",[51,7515,7516],{},"L'utilisation d'un système de fichier unique empêche l'utilisation de plusieurs serveurs de backups, synchronisés. (Je me dis que sur les grosses infrastructures, il peut-être intéressant de scaler horizontalement)",[51,7518,7519,7520],{},"Par contre, si je remplace Btrfs par autre chose, les questions sont alors",[48,7521,7522,7525,7528,7531],{},[51,7523,7524],{},"par quoi ?",[51,7526,7527],{},"Comment fais-je pour Rsync ?",[51,7529,7530],{},"Dois-je le remplacer aussi ?",[51,7532,7533],{},"Avoir mon propre client de sauvegarde (comme UrBackup) ?",[13,7535,7536],{},"Si vous avez des éléments de réponses, ou des idées là aussi, n'hésitez pas non plus à m'envoyer un mail.\nSi vous aussi n'êtes pas satisfaits de votre solution de sauvegardes, contactez-moi pour me dire ce qu'il vous faudrait, voir même pour contribuer.",[64,7538,7540],{"id":7539},"quelques-chiffres","Quelques chiffres",[13,7542,7543,7544,7546,7547,298],{},"À l'heure actuelle, je suis passé à ",[141,7545,7041],{}," pour mes sauvegardes en ayant migré toutes mes sauvegardes depuis ",[141,7548,7060],{},[13,7550,7551,7552,7554,7555,7557],{},"Je peux donc faire quelques comparaisons rapides (",[141,7553,7041],{}," méritant encore quelques évolutions pour atteindre le niveau de ",[141,7556,7060],{}," dans certains domaines).",[13,7559,7560],{},"Je possède 6 machines dont l'espace est répartit comme suite :",[7562,7563,7564,7577],"table",{},[7565,7566,7567],"thead",{},[7568,7569,7570,7574],"tr",{},[7571,7572,7573],"th",{},"Machine",[7571,7575,7576],{},"Stockage",[7578,7579,7580,7589,7597,7605,7613,7621],"tbody",{},[7568,7581,7582,7586],{},[7583,7584,7585],"td",{},"pc-windows",[7583,7587,7588],{},"260.8 Gb ",[7568,7590,7591,7594],{},[7583,7592,7593],{},"pc-portable-1",[7583,7595,7596],{},"148.1 Gb",[7568,7598,7599,7602],{},[7583,7600,7601],{},"pc-portable-2",[7583,7603,7604],{},"41.0 Gb",[7568,7606,7607,7610],{},[7583,7608,7609],{},"pc-linux",[7583,7611,7612],{},"153.3 Gb",[7568,7614,7615,7618],{},[7583,7616,7617],{},"nas",[7583,7619,7620],{},"1.4 Tb",[7568,7622,7623,7626],{},[7583,7624,7625],{},"server-ovh",[7583,7627,7628],{},"189.6 Gb",[13,7630,7631],{},"Certains fichiers (comme les photos de vacances, ...) peuvent être sur plusieurs machines (nas + pc fixe).",[7562,7633,7634,7646],{},[7565,7635,7636],{},[7568,7637,7638,7641,7643],{},[7571,7639,7640],{},"Comparaison",[7571,7642,7060],{},[7571,7644,7645],{},"Woodstock",[7578,7647,7648,7662,7673,7683,7694,7704,7715],{},[7568,7649,7650,7653,7656],{},[7583,7651,7652],{},"Pool de sauvegarde",[7583,7654,7655],{},"le stockage optimisé 1,88 To",[7583,7657,7658,7659,7661],{}," Le stockage ",[72,7660,7200],{}," avec compression prend 2,1 To",[7568,7663,7664,7667,7670],{},[7583,7665,7666],{},"Compression pc-windows",[7583,7668,7669],{},"357 Go non compressé",[7583,7671,7672],{},"326 Go non compressé",[7568,7674,7675,7677,7680],{},[7583,7676],{},[7583,7678,7679],{},"246 Go compressé",[7583,7681,7682],{},"245 Go compressé",[7568,7684,7685,7688,7691],{},[7583,7686,7687],{},"Compression pc-portable-1",[7583,7689,7690],{},"74 Go non compressé",[7583,7692,7693],{},"79 Go non compressé",[7568,7695,7696,7698,7701],{},[7583,7697],{},[7583,7699,7700],{},"70 Go compressé",[7583,7702,7703],{},"75 Go compressé",[7568,7705,7706,7709,7712],{},[7583,7707,7708],{},"Temps de sauvegarde incrémental NAS",[7583,7710,7711],{}," 21 minutes en moyenne",[7583,7713,7714],{},"15 minutes en moyenne",[7568,7716,7717,7720,7723],{},[7583,7718,7719],{},"Temps de sauvegarde incrémental pc-linux",[7583,7721,7722],{},"30 minutes en moyenne",[7583,7724,7725],{},"7 minutes en moyenne",[13,7727,7728],{},"J'ai l'impression que la notion de compress-force n'est pas prise en compte, et que la compression n'est pas des plus efficaces.",[13,7730,7731],{},"Pour la taille du pool de sauvegarde cela ne m'étonne pas vu que la déduplication n'est pas cross-machine.",[13,7733,7734],{},"Pour le temps de sauvegarde c'est une bonne surprise mais là aussi, je pense que c'est normal vu que rsync ne doit pas faire de déduplication à\nson niveau.",[4118,7736,7738,7741],{"className":7737,"dataFootnotes":104},[4121],[64,7739,4126],{"className":7740,"id":103},[4125],[4128,7742,7743,7749],{},[51,7744,7745,7746],{"id":4132},"A voir si je conserve ce mode de fonctionnement pour le futur. J'envisage de peut-être prendre un client lourd. ",[23,7747,4140],{"href":4136,"ariaLabel":4137,"className":7748,"dataFootnoteBackref":104},[4139],[51,7750,7751,7752],{"id":4143},"Mais si un UI\u002FUX souhaite améliorer l'interface, je suis preneur. ",[23,7753,4140],{"href":4147,"ariaLabel":4148,"className":7754,"dataFootnoteBackref":104},[4139],{"title":104,"searchDepth":178,"depth":178,"links":7756},[7757,7758,7763,7764,7765,7766,7767,7768,7769],{"id":7049,"depth":178,"text":7050},{"id":7144,"depth":178,"text":7145,"children":7759},[7760,7761,7762],{"id":7186,"depth":208,"text":7187},{"id":7242,"depth":208,"text":7243},{"id":7287,"depth":208,"text":7288},{"id":7327,"depth":178,"text":7328},{"id":7386,"depth":178,"text":7387},{"id":7404,"depth":178,"text":7405},{"id":7455,"depth":178,"text":7456},{"id":7487,"depth":178,"text":7488},{"id":7539,"depth":178,"text":7540},{"id":103,"depth":178,"text":4126},"woodstock","2020-09-20",{"type":10,"value":7773},[7774,7776,7782,7784,7786,7793,7799],[13,7775,7030],{},[13,7777,7033,7778,7038,7780,298],{},[23,7779,7037],{"href":7036},[141,7781,7041],{},[7043,7783,7046],{"id":7045},[64,7785,7050],{"id":7049},[13,7787,7053,7788,7061],{},[141,7789,7790],{},[23,7791,7060],{"href":7058,"rel":7792},[27],[13,7794,7795,7066,7797,7070],{},[141,7796,7060],{},[72,7798,7069],{},[13,7800,7073],{},{"planet":211},"\u002Fpost\u002Fwoodstock",{"title":7025,"description":7030},"posts\u002FWoodstock\u002F2020-09-20_woodstock",[6347,7806,7200,7807,6121,6122,7770],"sauvegarde","rsync","3u7RFr1fMKcgbLywQbn4HX3gySdxxhd7EFlJL3Est34",{"id":7810,"title":7811,"author":8,"body":7812,"category":7824,"categorySlug":8382,"date":8383,"description":7030,"excerpt":8384,"extension":4199,"location":4200,"meta":8404,"navigation":211,"path":8405,"published":211,"seo":8406,"slug":8407,"stem":8408,"tags":8409,"timeToRead":234,"__hash__":8411},"posts\u002Fposts\u002FPassprotect\u002F2020-07-27-fin-passprotect.md","Fin de Passprotect",{"type":10,"value":7813,"toc":8378},[7814,7816,7819,7825,7833,7841,7845,7848,7854,7857,7860,7863,7878,7881,7885,7891,7894,7897,7900,7908,7911,7920,7923,7927,7930,7952,7955,7957,7971,7980,8343,8350,8353,8355,8358,8361,8364,8375],[13,7815,7030],{},[13,7817,7818],{},"Cela fait longtemps que je n'ai pas écrit de billet de blog (3 ans et demi). Et il s'en est passé des choses. Je travaille actuellement sur un nouveau projet dont je reviendrai vous parler plus tard.",[13,7820,7821,7822,298],{},"A aujourd'hui je souhaiterais surtout vous parler de la fin de service de mon propre gestionnaire de mot de passe ",[151,7823,7824],{},"Passprotect",[13,7826,7827,7828,7832],{},"Ces dernières années, j'ai utilisé mon propre gestionnaire de mot de passe: ",[23,7829,7824],{"href":7830,"rel":7831},"https:\u002F\u002Fgogs.shadoware.org\u002Fphoenix\u002Fpassprotect-vue",[27]," développé par mes soins :).\nJe n'avais pas trouvé mon bonheur par ailleurs (OpenSource, Client WEB, ...) et avais décidé de développer le mien.",[13,7834,7835,7836,298],{},"Puis j'ai découvert ",[23,7837,7840],{"href":7838,"rel":7839},"https:\u002F\u002Fbitwarden.com\u002F",[27],"Bitwarden",[7043,7842,7844],{"id":7843},"la-fin","La Fin",[13,7846,7847],{},"L'avantage d'utiliser un logiciel fabriqué par soi-même est la fierté de l'utiliser et de voir d'autres personnes l'utiliser également.",[13,7849,7850,7851,7853],{},"Malheureusement ",[151,7852,7824],{}," ne possédait que deux utilisateurs actifs (et quelques Bot\u002FSPAM) et ne proposait pas les fonctionnalités que d'autres proposaient. J'aurais pu les développer mais n'ayant pas assez d'utilisateurs, je n'avais pas assez de motivation pour le faire.",[13,7855,7856],{},"J'ai développé mon gestionnaire de mot de passe, à moi, car je ne me retrouvais pas dans les gestionnaires de mot de passe existants. Puis un jour, par hasard, j'ai découvert bitwarden.",[13,7858,7859],{},"J'ai alors décidé de le tester, (et cela fait maintenant presque un an), et étant satisfait, j'ai décidé d'arrêter de développer mon gestionnaire de mot de passe pour me concenter sur d'autres logiciels.",[13,7861,7862],{},"Même si le nom de domaine servant l'application n'existe plus, les sources resteront néanmoins disponibles ici, si cela intéresse quelqu'un :",[48,7864,7865,7872],{},[51,7866,7867],{},[23,7868,7871],{"href":7869,"rel":7870},"https:\u002F\u002Fgogs.shadoware.org\u002Fphoenix\u002Fpassprotect-server",[27],"Le serveur",[51,7873,7874],{},[23,7875,7877],{"href":7830,"rel":7876},[27],"Le client",[13,7879,7880],{},"Si quelqu'un trouve que le projet vaut toujours le coup, qu'il n'hésite pas à me contacter ou à proposer des évolutions, ou à développer des nouvelles fonctionnalités.",[7043,7882,7884],{"id":7883},"le-début","Le début",[13,7886,7887,7888,298],{},"Parlons alors de ",[23,7889,7840],{"href":7838,"rel":7890},[27],[13,7892,7893],{},"D'un point de vue technique, c'est pas tout rose si on regarde la version officielle.",[13,7895,7896],{},"Ce qui est super, c'est qu'il existe un client pour Windows, Linux, MacOS, Android, Apple, les différents navigateurs et tout cela en OpenSource :).",[13,7898,7899],{},"Par contre la partie serveur (qui est également en OpenSource), nécessite :",[48,7901,7902,7905],{},[51,7903,7904],{},".NET Core 3.1 SDK",[51,7906,7907],{},"SQL Serveur 2017",[13,7909,7910],{},"Et je n'apprécie pas forcément ces deux technos. (Et je n'ai surtout pas envie d'installer une base SQL Serveur uniquement pour un gestionnaire de mot de passe).",[13,7912,7913,7914,7919],{},"Par contre pour la partie Serveur, il existe une alternative non officielle en RUST (un fork, quoi): ",[23,7915,7918],{"href":7916,"rel":7917},"https:\u002F\u002Fgithub.com\u002Fdani-garcia\u002Fbitwarden_rs",[27],"bitwarden_rs",". Le serveur peut alors utiliser une base MySQL, Sqlite, PostgresQL.",[13,7921,7922],{},"Je décide alors de l'installer et l'utiliser pour me faire un avis.",[64,7924,7926],{"id":7925},"quelles-sont-les-fonctionnalités-que-japprécie","Quelles sont les fonctionnalités que j'apprécie ?",[13,7928,7929],{},"Après quelques mois d'utilisation, voici ce que j'apprécie :",[48,7931,7932,7935,7938],{},[51,7933,7934],{},"Le partage de mot de passe entre compte",[51,7936,7937],{},"Les clients sous Android et dans les différents navigateurs.",[51,7939,7940,7941],{},"Les rapports d'utilisation des mots de passes :\n",[48,7942,7943,7946,7949],{},[51,7944,7945],{},"réutilisés",[51,7947,7948],{},"exposés",[51,7950,7951],{},"faibles",[13,7953,7954],{},"Je n'y ai pas trouvé de point que je n'apprécie pas (à part que ce n'est pas développé par moi même 😄).",[64,7956,7405],{"id":7404},[13,7958,7959,7960,4566,7963,7966,7967,7970],{},"Il faudra que je vous parle de mon infra-structure et de comment j'héberge mes différents sites, mais en gros je me base entièrement sur ",[151,7961,7962],{},"Docker",[151,7964,7965],{},"Docker Swarm",". Du coup, l'hébergement de ",[151,7968,7969],{},"bitwarden"," également passe par là.",[13,7972,7973,7974,7977,7978,7100],{},"Voici mon fichier ",[151,7975,7976],{},"docker-compose.yml"," qui injecté à ",[151,7979,7965],{},[145,7981,7985],{"className":7982,"code":7983,"language":7984,"meta":104,"style":104},"language-yaml shiki shiki-themes one-dark-pro","version: \"3.4\"\n\nnetworks:\n  traefik-swarm:\n    external:\n      name: traefik-swarm\n  sendmail:\n    external:\n      name: sendmail\n\nservices:\n  bitwarden:\n    image: bitwardenrs\u002Fserver\n    volumes:\n      - \u002Fbitwarden\u002Fdata:\u002Fdata\n    environment:\n      - WEBSOCKET_ENABLED=true\n      - SIGNUPS_ALLOWED=false\n      - DOMAIN=https:\u002F\u002Fbitwrd.shadoware.org\n      - SMTP_HOST=postfix_relay\n      - SMTP_FROM=bitwrd@shadoware.org\n      - SMTP_PORT=25\n      - SMTP_SSL=false\n    networks:\n      - traefik-swarm\n      - sendmail\n    deploy:\n      labels:\n        - traefik.enable=true\n        - traefik.http.routers.bitwarden_server_router.rule=Host(`bitwrd.{{ domains_default }}`)\n        - traefik.http.routers.bitwarden_server_router.entrypoints=web\n        - traefik.http.routers.bitwarden_server_router.middlewares=redirecthttps@file,bitwarden_headers\n        - traefik.http.routers.bitwarden_server_router_https.rule=Host(`bitwrd.{{ domains_default }}`)\n        - traefik.http.routers.bitwarden_server_router_https.middlewares=bitwarden_headers\n        - traefik.http.routers.bitwarden_server_router_https.tls\n        - traefik.http.routers.bitwarden_server_router_https.tls.certresolver=shadoware\n        - traefik.http.routers.bitwarden_server_router_https.tls.domains[0].main={{ domains_default }}\n        - traefik.http.routers.bitwarden_server_router_https.tls.domains[0].sans=*.{{ domains_default }}\n        - traefik.http.services.bitwarden_server.loadbalancer.server.port=80\n        - traefik.http.middlewares.bitwarden_headers.headers.sslRedirect=true\n        - traefik.http.middlewares.bitwarden_headers.headers.browserXssFilter=true\n        - traefik.http.middlewares.bitwarden_headers.headers.contentTypeNosniff=true\n        - traefik.http.middlewares.bitwarden_headers.headers.sslHost=bitwrd.{{ domains_default }}\n        - traefik.http.middlewares.bitwarden_headers.headers.frameDeny=true\n      restart_policy:\n        condition: any\n      update_config:\n        order: start-first\n","yaml",[151,7986,7987,7997,8001,8009,8016,8023,8033,8040,8046,8055,8059,8066,8073,8083,8090,8098,8105,8112,8119,8126,8133,8140,8147,8154,8161,8167,8173,8180,8187,8195,8202,8209,8216,8223,8230,8237,8244,8251,8258,8265,8273,8281,8289,8297,8305,8313,8324,8332],{"__ignoreMap":104},[154,7988,7989,7992,7994],{"class":156,"line":157},[154,7990,7991],{"class":193},"version",[154,7993,4314],{"class":174},[154,7995,7996],{"class":201},"\"3.4\"\n",[154,7998,7999],{"class":156,"line":178},[154,8000,212],{"emptyLinePlaceholder":211},[154,8002,8003,8006],{"class":156,"line":208},[154,8004,8005],{"class":193},"networks",[154,8007,8008],{"class":174},":\n",[154,8010,8011,8014],{"class":156,"line":215},[154,8012,8013],{"class":193},"  traefik-swarm",[154,8015,8008],{"class":174},[154,8017,8018,8021],{"class":156,"line":234},[154,8019,8020],{"class":193},"    external",[154,8022,8008],{"class":174},[154,8024,8025,8028,8030],{"class":156,"line":256},[154,8026,8027],{"class":193},"      name",[154,8029,4314],{"class":174},[154,8031,8032],{"class":201},"traefik-swarm\n",[154,8034,8035,8038],{"class":156,"line":374},[154,8036,8037],{"class":193},"  sendmail",[154,8039,8008],{"class":174},[154,8041,8042,8044],{"class":156,"line":402},[154,8043,8020],{"class":193},[154,8045,8008],{"class":174},[154,8047,8048,8050,8052],{"class":156,"line":427},[154,8049,8027],{"class":193},[154,8051,4314],{"class":174},[154,8053,8054],{"class":201},"sendmail\n",[154,8056,8057],{"class":156,"line":450},[154,8058,212],{"emptyLinePlaceholder":211},[154,8060,8061,8064],{"class":156,"line":458},[154,8062,8063],{"class":193},"services",[154,8065,8008],{"class":174},[154,8067,8068,8071],{"class":156,"line":463},[154,8069,8070],{"class":193},"  bitwarden",[154,8072,8008],{"class":174},[154,8074,8075,8078,8080],{"class":156,"line":485},[154,8076,8077],{"class":193},"    image",[154,8079,4314],{"class":174},[154,8081,8082],{"class":201},"bitwardenrs\u002Fserver\n",[154,8084,8085,8088],{"class":156,"line":499},[154,8086,8087],{"class":193},"    volumes",[154,8089,8008],{"class":174},[154,8091,8092,8095],{"class":156,"line":504},[154,8093,8094],{"class":174},"      - ",[154,8096,8097],{"class":201},"\u002Fbitwarden\u002Fdata:\u002Fdata\n",[154,8099,8100,8103],{"class":156,"line":509},[154,8101,8102],{"class":193},"    environment",[154,8104,8008],{"class":174},[154,8106,8107,8109],{"class":156,"line":526},[154,8108,8094],{"class":174},[154,8110,8111],{"class":201},"WEBSOCKET_ENABLED=true\n",[154,8113,8114,8116],{"class":156,"line":549},[154,8115,8094],{"class":174},[154,8117,8118],{"class":201},"SIGNUPS_ALLOWED=false\n",[154,8120,8121,8123],{"class":156,"line":563},[154,8122,8094],{"class":174},[154,8124,8125],{"class":201},"DOMAIN=https:\u002F\u002Fbitwrd.shadoware.org\n",[154,8127,8128,8130],{"class":156,"line":569},[154,8129,8094],{"class":174},[154,8131,8132],{"class":201},"SMTP_HOST=postfix_relay\n",[154,8134,8135,8137],{"class":156,"line":786},[154,8136,8094],{"class":174},[154,8138,8139],{"class":201},"SMTP_FROM=bitwrd@shadoware.org\n",[154,8141,8142,8144],{"class":156,"line":797},[154,8143,8094],{"class":174},[154,8145,8146],{"class":201},"SMTP_PORT=25\n",[154,8148,8149,8151],{"class":156,"line":818},[154,8150,8094],{"class":174},[154,8152,8153],{"class":201},"SMTP_SSL=false\n",[154,8155,8156,8159],{"class":156,"line":838},[154,8157,8158],{"class":193},"    networks",[154,8160,8008],{"class":174},[154,8162,8163,8165],{"class":156,"line":854},[154,8164,8094],{"class":174},[154,8166,8032],{"class":201},[154,8168,8169,8171],{"class":156,"line":882},[154,8170,8094],{"class":174},[154,8172,8054],{"class":201},[154,8174,8175,8178],{"class":156,"line":887},[154,8176,8177],{"class":193},"    deploy",[154,8179,8008],{"class":174},[154,8181,8182,8185],{"class":156,"line":892},[154,8183,8184],{"class":193},"      labels",[154,8186,8008],{"class":174},[154,8188,8189,8192],{"class":156,"line":899},[154,8190,8191],{"class":174},"        - ",[154,8193,8194],{"class":201},"traefik.enable=true\n",[154,8196,8197,8199],{"class":156,"line":910},[154,8198,8191],{"class":174},[154,8200,8201],{"class":201},"traefik.http.routers.bitwarden_server_router.rule=Host(`bitwrd.{{ domains_default }}`)\n",[154,8203,8204,8206],{"class":156,"line":921},[154,8205,8191],{"class":174},[154,8207,8208],{"class":201},"traefik.http.routers.bitwarden_server_router.entrypoints=web\n",[154,8210,8211,8213],{"class":156,"line":931},[154,8212,8191],{"class":174},[154,8214,8215],{"class":201},"traefik.http.routers.bitwarden_server_router.middlewares=redirecthttps@file,bitwarden_headers\n",[154,8217,8218,8220],{"class":156,"line":946},[154,8219,8191],{"class":174},[154,8221,8222],{"class":201},"traefik.http.routers.bitwarden_server_router_https.rule=Host(`bitwrd.{{ domains_default }}`)\n",[154,8224,8225,8227],{"class":156,"line":961},[154,8226,8191],{"class":174},[154,8228,8229],{"class":201},"traefik.http.routers.bitwarden_server_router_https.middlewares=bitwarden_headers\n",[154,8231,8232,8234],{"class":156,"line":976},[154,8233,8191],{"class":174},[154,8235,8236],{"class":201},"traefik.http.routers.bitwarden_server_router_https.tls\n",[154,8238,8239,8241],{"class":156,"line":1000},[154,8240,8191],{"class":174},[154,8242,8243],{"class":201},"traefik.http.routers.bitwarden_server_router_https.tls.certresolver=shadoware\n",[154,8245,8246,8248],{"class":156,"line":1005},[154,8247,8191],{"class":174},[154,8249,8250],{"class":201},"traefik.http.routers.bitwarden_server_router_https.tls.domains[0].main={{ domains_default }}\n",[154,8252,8253,8255],{"class":156,"line":5920},[154,8254,8191],{"class":174},[154,8256,8257],{"class":201},"traefik.http.routers.bitwarden_server_router_https.tls.domains[0].sans=*.{{ domains_default }}\n",[154,8259,8260,8262],{"class":156,"line":5926},[154,8261,8191],{"class":174},[154,8263,8264],{"class":201},"traefik.http.services.bitwarden_server.loadbalancer.server.port=80\n",[154,8266,8268,8270],{"class":156,"line":8267},40,[154,8269,8191],{"class":174},[154,8271,8272],{"class":201},"traefik.http.middlewares.bitwarden_headers.headers.sslRedirect=true\n",[154,8274,8276,8278],{"class":156,"line":8275},41,[154,8277,8191],{"class":174},[154,8279,8280],{"class":201},"traefik.http.middlewares.bitwarden_headers.headers.browserXssFilter=true\n",[154,8282,8284,8286],{"class":156,"line":8283},42,[154,8285,8191],{"class":174},[154,8287,8288],{"class":201},"traefik.http.middlewares.bitwarden_headers.headers.contentTypeNosniff=true\n",[154,8290,8292,8294],{"class":156,"line":8291},43,[154,8293,8191],{"class":174},[154,8295,8296],{"class":201},"traefik.http.middlewares.bitwarden_headers.headers.sslHost=bitwrd.{{ domains_default }}\n",[154,8298,8300,8302],{"class":156,"line":8299},44,[154,8301,8191],{"class":174},[154,8303,8304],{"class":201},"traefik.http.middlewares.bitwarden_headers.headers.frameDeny=true\n",[154,8306,8308,8311],{"class":156,"line":8307},45,[154,8309,8310],{"class":193},"      restart_policy",[154,8312,8008],{"class":174},[154,8314,8316,8319,8321],{"class":156,"line":8315},46,[154,8317,8318],{"class":193},"        condition",[154,8320,4314],{"class":174},[154,8322,8323],{"class":201},"any\n",[154,8325,8327,8330],{"class":156,"line":8326},47,[154,8328,8329],{"class":193},"      update_config",[154,8331,8008],{"class":174},[154,8333,8335,8338,8340],{"class":156,"line":8334},48,[154,8336,8337],{"class":193},"        order",[154,8339,4314],{"class":174},[154,8341,8342],{"class":201},"start-first\n",[13,8344,8345,8346,8349],{},"Comme vous pouvez le voir, j'utilise ",[151,8347,8348],{},"traefik"," pour router les noms de domaines vers les bonnes images.\nDans mon infrastructure j'essaye d'isoler au mieux chaque application et utilise ensuite les différents réseaux pour les faire communiquer.",[13,8351,8352],{},"Par exemple, cette application communique avec le réseau d'envoi de mail et aussi avec celui de traefik pour sortir sur Internet.",[7043,8354,4079],{"id":4078},[13,8356,8357],{},"Ce que j'ai appris avec tout cela : j'ai apprécié faire mon propre gestionnaire de mot de passe. En développant ce dernier, j'ai appris quelques trucs sur comment bien gérer le chiffrage des mots de passes, de la bonne utilisation du sel, par exemple.",[13,8359,8360],{},"Ce que je savais déjà par contre, c'est qu'il est très difficile de concurrencer d'autres structures (comme Lastpass, BitWarden, ...) que beaucoup de monde utilise déjà.",[13,8362,8363],{},"Et je rappelle également que quelque soit la solution que vous choisissez pour stocker vos mots de passes, il est important :",[48,8365,8366,8369,8372],{},[51,8367,8368],{},"d'avoir un mot de passe différent par site internet, ainsi si un site se fait pirater, ce ne sont pas tous vos mots de passe qui sont à remplacer,",[51,8370,8371],{},"avoir un gestionnaire de mot de passe sécurisé est important, car si c'est ce dernier qui se fait véroler, vous devrez renouveller tous vos mots de passes,",[51,8373,8374],{},"utiliser un gestionnaire de mot de passe permet aussi de savoir tous les sites où on laisse une trace. En effet il est souvent très facile de créer un compte pour un site puis de ne jamais y retourner avant plusieurs années. Avoir une trace de ces sites peut permettre de les supprimer régulièrement et de savoir où on a laissé une trace numérique.",[4151,8376,8377],{},"html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":104,"searchDepth":178,"depth":178,"links":8379},[8380,8381],{"id":7925,"depth":178,"text":7926},{"id":7404,"depth":178,"text":7405},"passprotect","2020-07-27",{"type":10,"value":8385},[8386,8388,8390,8394,8399],[13,8387,7030],{},[13,8389,7818],{},[13,8391,7821,8392,298],{},[151,8393,7824],{},[13,8395,7827,8396,7832],{},[23,8397,7824],{"href":7830,"rel":8398},[27],[13,8400,7835,8401,298],{},[23,8402,7840],{"href":7838,"rel":8403},[27],{"planet":211},"\u002Fpost\u002Ffin-passprotect",{"title":7811,"description":7030},"fin-passprotect","posts\u002FPassprotect\u002F2020-07-27-fin-passprotect",[8382,6121,6122,8410,7969],"password","aBn055QA5xKVE20jUEfM0UAavpQjMuzZ-j1dg5xODUg",{"id":8413,"title":8414,"author":8,"body":8415,"category":4164,"categorySlug":4165,"date":9879,"description":104,"excerpt":9880,"extension":4199,"location":4200,"meta":9901,"navigation":211,"path":9902,"published":211,"seo":9903,"slug":9904,"stem":9905,"tags":9906,"timeToRead":463,"__hash__":9911},"posts\u002Fposts\u002FProgrammation\u002F2016-12-10-findsimilarity.md","FindSimilarity - Trouver les différences entre plusieurs vidéos",{"type":10,"value":8416,"toc":9867},[8417,8421,8423,8426,8429,8432,8437,8444,8448,8451,8454,8487,8490,8541,8545,8548,8562,8568,8576,8579,8588,8591,8594,8597,8993,8997,9000,9009,9012,9015,9041,9044,9168,9171,9179,9194,9197,9200,9739,9749,9753,9756,9760,9763,9766,9772,9775,9779,9785,9795,9798,9815,9832,9836,9839,9845,9848,9854,9859,9861,9864],[133,8418,8420],{"id":8419},"introduction","Introduction",[13,8422,4216],{},[13,8424,8425],{},"Je souhaite vous présenter une petite expérience que je viens d'écrire.",[13,8427,8428],{},"Cela fait plusieurs années que je souhaitais m'amuser sur la librairie OpenCV mais sans jamais en avoir eu l'utilité. J'ai profité d'avoir un peu de temps libre, pour écrire un petit\nprogramme dont le but est de comparer un ensemble de vidéos.",[13,8430,8431],{},"Le but est ensuite de dire si dans cet ensemble de vidéos, deux vidéos sont identiques, ou se ressemblent, ou sont trop éloignées.",[13,8433,8434],{},[72,8435,8436],{},"J'ai souhaité faire cette expérience par amusement, je n'ai donc pas passé beaucoup de temps sur la qualité du code écrit. Ce dernier aurait pu être mieux découpé, posséder des\ncommentaires, des tests unitaires, ... . Si vous voulez utiliser ce code pour un véritable usage production, n'hésitez pas à améliorer celui ci.",[13,8438,8439,8440,298],{},"Vous pouvez trouver le code source de cette expérience à l'adresse suivante : ",[23,8441,8442],{"href":8442,"rel":8443},"https:\u002F\u002Fgogs.shadoware.org\u002FShadowareOrg\u002Ffind-similarity",[27],[133,8445,8447],{"id":8446},"le-jeux-de-données","Le jeux de données",[13,8449,8450],{},"J'ai pris plusieurs films en DVD que je possède. Possédant un NAS, et une chromecast, j'encode ces DVD au format vidéo et je les y dépose. Malheureusement la qualité est dégradée\npar rapport au DVD.",[13,8452,8453],{},"Pour constituer le jeu de données, je prends ces films encodés, que je dépose dans un dossier. Je copie certains d'entre eux telquel",[145,8455,8459],{"className":8456,"code":8457,"language":8458,"meta":104,"style":104},"language-bash shiki shiki-themes one-dark-pro","mkdir example\ncd example\ncp ..\u002Ffilm1.avi film1_copy.avi\n","bash",[151,8460,8461,8469,8476],{"__ignoreMap":104},[154,8462,8463,8466],{"class":156,"line":157},[154,8464,8465],{"class":348},"mkdir",[154,8467,8468],{"class":201}," example\n",[154,8470,8471,8474],{"class":156,"line":178},[154,8472,8473],{"class":197},"cd",[154,8475,8468],{"class":201},[154,8477,8478,8481,8484],{"class":156,"line":208},[154,8479,8480],{"class":348},"cp",[154,8482,8483],{"class":201}," ..\u002Ffilm1.avi",[154,8485,8486],{"class":201}," film1_copy.avi\n",[13,8488,8489],{},"J'encode certains de ces films avec une résolution différente :",[145,8491,8493],{"className":8456,"code":8492,"language":8458,"meta":104,"style":104},"avconv -i film2.m4v  -preset veryslow -s 320x240 film2.320x240.m4v\navconv -i film2.m4v  -preset veryslow -s 640x480 film2.640x480.m4v\n",[151,8494,8495,8521],{"__ignoreMap":104},[154,8496,8497,8500,8503,8506,8509,8512,8515,8518],{"class":156,"line":157},[154,8498,8499],{"class":348},"avconv",[154,8501,8502],{"class":228}," -i",[154,8504,8505],{"class":201}," film2.m4v",[154,8507,8508],{"class":228},"  -preset",[154,8510,8511],{"class":201}," veryslow",[154,8513,8514],{"class":228}," -s",[154,8516,8517],{"class":201}," 320x240",[154,8519,8520],{"class":201}," film2.320x240.m4v\n",[154,8522,8523,8525,8527,8529,8531,8533,8535,8538],{"class":156,"line":178},[154,8524,8499],{"class":348},[154,8526,8502],{"class":228},[154,8528,8505],{"class":201},[154,8530,8508],{"class":228},[154,8532,8511],{"class":201},[154,8534,8514],{"class":228},[154,8536,8537],{"class":201}," 640x480",[154,8539,8540],{"class":201}," film2.640x480.m4v\n",[133,8542,8544],{"id":8543},"comment-fonctionne-la-comparaison","Comment fonctionne la comparaison",[13,8546,8547],{},"Avant de parler de la comparaison, parlons des fichiers que nous allons comparer. Le programme va se constituer une liste des fichiers à comparer et pour chaque fichier va lire\nles informations suivantes :",[48,8549,8550,8553,8556,8559],{},[51,8551,8552],{},"la durée du film",[51,8554,8555],{},"la largeur",[51,8557,8558],{},"la hauteur",[51,8560,8561],{},"une miniature du film (utilisée pour comparer à l'oeil les vidéos)",[13,8563,8564,8565,298],{},"Ensuite, une fois les méta-données récupérées, le programe se constitue une liste de paires de fichiers à comparer en sélectionnant les fichiers qui ont une durée identique à +\u002F- 5\nsecondes. Ce paramètre est modifiable au niveau de la constante ",[151,8566,8567],{},"DELTA_SEC",[13,8569,8570,8571,298],{},"Enfin vient la comparaison pour laquelle je me suis simplement basé sur les exemples du site OpenCV que vous pouvez trouver dans la rubrique\n",[23,8572,8575],{"href":8573,"rel":8574},"http:\u002F\u002Fdocs.opencv.org\u002F2.4\u002Fdoc\u002Ftutorials\u002Fhighgui\u002Fvideo-input-psnr-ssim\u002Fvideo-input-psnr-ssim.html",[27],"Video Input with OpenCV and similarity measurement",[13,8577,8578],{},"J'ai utilisé l'algorithme PSNR (Peak signal-to-noise ratio) pour déterminer si les deux images de la vidéos sont plutôt proches ou éloignées. Cet algorithme calcul la distorsion\nentre deux images. Il est principalement utilisé pour quantifier la performance réalisée par un encodeur lors de la compression d'une vidéo. Une valeur entre 30 et 50 signifie que\nles images sont relativement proches. Plus la valeur est haute, et plus la qualité d'image est conservée entre les deux images. Si la valeur est inférieure à 30 on peut estimer qu'il\ny a une forte chance pour que les images soit différentes.",[13,8580,8581,8582,8587],{},"Vous pouvez retrouver les formules utilisées par le calcul sur le site d'OpenCV ou sur la page ",[23,8583,8586],{"href":8584,"rel":8585},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FPeak_Signal_to_Noise_Ratio",[27],"Wikipedia",". Est-ce que ce\ncalcul est le meilleur pour trouver les images similaires ? Je ne sais pas. Si vous avez d'autres propositions, on peut les tester.",[13,8589,8590],{},"Sur une vidéo on a une multitude d'images (sur un film d'une heure et demie à 25 images secondes, nous en avons 135 000), on pourrait comparer chaque image de la vidéo pour se faire\nune moyenne, de mon coté j'ai préféré comparer une image au milieu de la vidéo afin de parcourir plus vite les vidéos.",[13,8592,8593],{},"De la même manière pour m'abstraire de la taille de la vidéo qui peut avoir été modififée, je redimensionne, à tort ou à raison, les deux images à une taille identique\n(arbitrairement: 160x120).",[13,8595,8596],{},"Je vous présente donc ci-dessous l'algorithme que vous pouvez retrouver sur le site d'OpenCV. J'ai légèrement modifié l'algorithme pour redimensionner les images ainsi que pour\nretourner une valeur de PSNR infiniment grande quand deux vidéos sont identiques.",[145,8598,8602],{"className":8599,"code":8600,"language":8601,"meta":104,"style":104},"language-cpp shiki shiki-themes one-dark-pro","double getPSNR(const cv::Mat& F1, const cv::Mat& F2) {\n    cv::Mat I1, I2;\n\n    cv::resize(F1, I1, cv::Size(160, 120));\n    cv::resize(F2, I2, cv::Size(160, 120));\n\n    cv::Mat s1;\n    cv::absdiff(I1, I2, s1);   \u002F\u002F |I1 - I2|\n    s1.convertTo(s1, CV_32F);  \u002F\u002F cannot make a square on 8 bits\n    s1 = s1.mul(s1);           \u002F\u002F |I1 - I2|^2\n\n    cv::Scalar s = sum(s1);    \u002F\u002F sum elements per channel\n\n    double sse = s.val[0] + s.val[1] + s.val[2]; \u002F\u002F sum channels\n\n    if( sse \u003C= 1e-10) {        \u002F\u002F for small values return zero\n        return std::numeric_limits\u003Cdouble>::infinity();\n    } else {\n        double mse  = sse \u002F (double)(I1.channels() * I1.total());\n        double psnr = 10.0 * log10((255 * 255) \u002F mse);\n        return psnr;\n    }\n","cpp",[151,8603,8604,8644,8649,8653,8680,8701,8705,8710,8723,8739,8760,8764,8779,8783,8841,8845,8870,8892,8901,8946,8982,8989],{"__ignoreMap":104},[154,8605,8606,8609,8612,8614,8617,8620,8623,8626,8629,8631,8633,8635,8637,8639,8642],{"class":156,"line":157},[154,8607,8608],{"class":160},"double",[154,8610,8611],{"class":348}," getPSNR",[154,8613,352],{"class":174},[154,8615,8616],{"class":160},"const",[154,8618,8619],{"class":174}," cv::",[154,8621,8622],{"class":164},"Mat",[154,8624,8625],{"class":160},"&",[154,8627,8628],{"class":358}," F1",[154,8630,2877],{"class":174},[154,8632,8616],{"class":160},[154,8634,8619],{"class":174},[154,8636,8622],{"class":164},[154,8638,8625],{"class":160},[154,8640,8641],{"class":358}," F2",[154,8643,815],{"class":174},[154,8645,8646],{"class":156,"line":178},[154,8647,8648],{"class":174},"    cv::Mat I1, I2;\n",[154,8650,8651],{"class":156,"line":208},[154,8652,212],{"emptyLinePlaceholder":211},[154,8654,8655,8658,8661,8664,8667,8669,8672,8674,8677],{"class":156,"line":215},[154,8656,8657],{"class":174},"    cv::",[154,8659,8660],{"class":348},"resize",[154,8662,8663],{"class":174},"(F1, I1, cv::",[154,8665,8666],{"class":348},"Size",[154,8668,352],{"class":174},[154,8670,8671],{"class":228},"160",[154,8673,2877],{"class":174},[154,8675,8676],{"class":228},"120",[154,8678,8679],{"class":174},"));\n",[154,8681,8682,8684,8686,8689,8691,8693,8695,8697,8699],{"class":156,"line":234},[154,8683,8657],{"class":174},[154,8685,8660],{"class":348},[154,8687,8688],{"class":174},"(F2, I2, cv::",[154,8690,8666],{"class":348},[154,8692,352],{"class":174},[154,8694,8671],{"class":228},[154,8696,2877],{"class":174},[154,8698,8676],{"class":228},[154,8700,8679],{"class":174},[154,8702,8703],{"class":156,"line":256},[154,8704,212],{"emptyLinePlaceholder":211},[154,8706,8707],{"class":156,"line":374},[154,8708,8709],{"class":174},"    cv::Mat s1;\n",[154,8711,8712,8714,8717,8720],{"class":156,"line":402},[154,8713,8657],{"class":174},[154,8715,8716],{"class":348},"absdiff",[154,8718,8719],{"class":174},"(I1, I2, s1);",[154,8721,8722],{"class":647},"   \u002F\u002F |I1 - I2|\n",[154,8724,8725,8728,8730,8733,8736],{"class":156,"line":427},[154,8726,8727],{"class":164},"    s1",[154,8729,298],{"class":174},[154,8731,8732],{"class":348},"convertTo",[154,8734,8735],{"class":174},"(s1, CV_32F);",[154,8737,8738],{"class":647},"  \u002F\u002F cannot make a square on 8 bits\n",[154,8740,8741,8744,8746,8749,8751,8754,8757],{"class":156,"line":450},[154,8742,8743],{"class":174},"    s1 ",[154,8745,198],{"class":160},[154,8747,8748],{"class":164}," s1",[154,8750,298],{"class":174},[154,8752,8753],{"class":348},"mul",[154,8755,8756],{"class":174},"(s1);",[154,8758,8759],{"class":647},"           \u002F\u002F |I1 - I2|^2\n",[154,8761,8762],{"class":156,"line":458},[154,8763,212],{"emptyLinePlaceholder":211},[154,8765,8766,8769,8771,8774,8776],{"class":156,"line":463},[154,8767,8768],{"class":174},"    cv::Scalar s ",[154,8770,198],{"class":160},[154,8772,8773],{"class":348}," sum",[154,8775,8756],{"class":174},[154,8777,8778],{"class":647},"    \u002F\u002F sum elements per channel\n",[154,8780,8781],{"class":156,"line":485},[154,8782,212],{"emptyLinePlaceholder":211},[154,8784,8785,8788,8791,8793,8796,8798,8801,8803,8805,8808,8811,8813,8815,8817,8819,8821,8823,8825,8827,8829,8831,8833,8835,8838],{"class":156,"line":499},[154,8786,8787],{"class":160},"    double",[154,8789,8790],{"class":174}," sse ",[154,8792,198],{"class":160},[154,8794,8795],{"class":164}," s",[154,8797,298],{"class":174},[154,8799,8800],{"class":193},"val",[154,8802,1271],{"class":174},[154,8804,926],{"class":228},[154,8806,8807],{"class":174},"] ",[154,8809,8810],{"class":160},"+",[154,8812,8795],{"class":164},[154,8814,298],{"class":174},[154,8816,8800],{"class":193},[154,8818,1271],{"class":174},[154,8820,106],{"class":228},[154,8822,8807],{"class":174},[154,8824,8810],{"class":160},[154,8826,8795],{"class":164},[154,8828,298],{"class":174},[154,8830,8800],{"class":193},[154,8832,1271],{"class":174},[154,8834,1447],{"class":228},[154,8836,8837],{"class":174},"];",[154,8839,8840],{"class":647}," \u002F\u002F sum channels\n",[154,8842,8843],{"class":156,"line":504},[154,8844,212],{"emptyLinePlaceholder":211},[154,8846,8847,8850,8853,8856,8858,8860,8862,8864,8867],{"class":156,"line":509},[154,8848,8849],{"class":160},"    if",[154,8851,8852],{"class":174},"( sse ",[154,8854,8855],{"class":160},"\u003C=",[154,8857,3559],{"class":228},[154,8859,1087],{"class":193},[154,8861,3230],{"class":174},[154,8863,6535],{"class":228},[154,8865,8866],{"class":174},") {",[154,8868,8869],{"class":647},"        \u002F\u002F for small values return zero\n",[154,8871,8872,8874,8877,8880,8882,8884,8887,8890],{"class":156,"line":526},[154,8873,1351],{"class":160},[154,8875,8876],{"class":174}," std::",[154,8878,8879],{"class":164},"numeric_limits",[154,8881,293],{"class":174},[154,8883,8608],{"class":160},[154,8885,8886],{"class":174},">::",[154,8888,8889],{"class":348},"infinity",[154,8891,610],{"class":174},[154,8893,8894,8897,8899],{"class":156,"line":549},[154,8895,8896],{"class":174},"    } ",[154,8898,1975],{"class":160},[154,8900,175],{"class":174},[154,8902,8903,8906,8909,8911,8913,8916,8918,8920,8923,8926,8928,8931,8934,8936,8939,8941,8944],{"class":156,"line":563},[154,8904,8905],{"class":160},"        double",[154,8907,8908],{"class":174}," mse  ",[154,8910,198],{"class":160},[154,8912,8790],{"class":174},[154,8914,8915],{"class":160},"\u002F",[154,8917,806],{"class":174},[154,8919,8608],{"class":160},[154,8921,8922],{"class":174},")(",[154,8924,8925],{"class":164},"I1",[154,8927,298],{"class":174},[154,8929,8930],{"class":348},"channels",[154,8932,8933],{"class":174},"() ",[154,8935,4584],{"class":160},[154,8937,8938],{"class":164}," I1",[154,8940,298],{"class":174},[154,8942,8943],{"class":348},"total",[154,8945,879],{"class":174},[154,8947,8948,8950,8953,8955,8958,8961,8964,8967,8970,8972,8975,8977,8979],{"class":156,"line":569},[154,8949,8905],{"class":160},[154,8951,8952],{"class":174}," psnr ",[154,8954,198],{"class":160},[154,8956,8957],{"class":228}," 10.0",[154,8959,8960],{"class":160}," *",[154,8962,8963],{"class":348}," log10",[154,8965,8966],{"class":174},"((",[154,8968,8969],{"class":228},"255",[154,8971,8960],{"class":160},[154,8973,8974],{"class":228}," 255",[154,8976,1957],{"class":174},[154,8978,8915],{"class":160},[154,8980,8981],{"class":174}," mse);\n",[154,8983,8984,8986],{"class":156,"line":786},[154,8985,1351],{"class":160},[154,8987,8988],{"class":174}," psnr;\n",[154,8990,8991],{"class":156,"line":797},[154,8992,367],{"class":174},[133,8994,8996],{"id":8995},"optimisation","Optimisation",[13,8998,8999],{},"La raison qui fait que je voulais m'amuser avec OpenCV c'est qu'il permet de faire ces calculs à l'aide du GPU au lieu du CPU.",[13,9001,9002,9003,9008],{},"L'utilisation du GPU permet d'améliorer la vitesse de calcul pour tout ce qui est traitement d'image, ce pour quoi un GPU est prévu pour. Pour plus d'informations sur\nl'utilisation du GPU dans OpenCV peut être trouvé sur la page ",[23,9004,9007],{"href":9005,"rel":9006},"http:\u002F\u002Fopencv.org\u002Fplatforms\u002Fcuda.html",[27],"CUDA"," d'OpenCV.",[13,9010,9011],{},"Le problème est que sur la version de Debian jessie que j'utilise, OpenCV n'est pas compilé avec CUDA, et ne permet donc pas d'utiliser le GPU. J'ai donc dû compiler ma propre\nversion d'OpenCV.",[13,9013,9014],{},"Pour cela la première étape consiste à récupérer le code source et à se positionner sur la branche que l'on souhaite compiler. Pour ma part je préfère compiler sur la branche 2.4,\nplus proche de la version de Debian.",[145,9016,9018],{"className":8456,"code":9017,"language":8458,"meta":104,"style":104},"git clone https:\u002F\u002Fgithub.com\u002Fopencv\u002Fopencv.git\ngit checkout 2.4\n",[151,9019,9020,9031],{"__ignoreMap":104},[154,9021,9022,9025,9028],{"class":156,"line":157},[154,9023,9024],{"class":348},"git",[154,9026,9027],{"class":201}," clone",[154,9029,9030],{"class":201}," https:\u002F\u002Fgithub.com\u002Fopencv\u002Fopencv.git\n",[154,9032,9033,9035,9038],{"class":156,"line":178},[154,9034,9024],{"class":348},[154,9036,9037],{"class":201}," checkout",[154,9039,9040],{"class":228}," 2.4\n",[13,9042,9043],{},"Viens ensuite la compilation :",[145,9045,9047],{"className":8456,"code":9046,"language":8458,"meta":104,"style":104},"mkdir build\ncd build\ncmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=\u002Fhome\u002Fphoenix\u002Fusr\u002Flocal  -DENABLE_SSE=ON -DENABLE_SSE2=ON -DENABLE_SSE3=ON -DWITH_TBB=ON -DWITH_1394=ON -DWITH_V4L=ON -DWITH_OPENGL=ON  -DWITH_GTK=ON -DWITH_JASPER=ON -DWITH_JPEG=ON -DWITH_PNG=ON -DWITH_TIFF=ON  -DWITH_OPENEXR=ON -DWITH_PVAPI=ON   -DWITH_EIGEN=ON -DCMAKE_SKIP_RPATH=ON -D WITH_CUDA=ON -D ENABLE_FAST_MATH=1 -D CUDA_FAST_MATH=1 -D WITH_CUBLAS=1 -DWITH_IPP=ON -D CUDA_GENERATION=Auto -D WITH_FFMPEG=ON  ..\u002F\n",[151,9048,9049,9056,9062],{"__ignoreMap":104},[154,9050,9051,9053],{"class":156,"line":157},[154,9052,8465],{"class":348},[154,9054,9055],{"class":201}," build\n",[154,9057,9058,9060],{"class":156,"line":178},[154,9059,8473],{"class":197},[154,9061,9055],{"class":201},[154,9063,9064,9067,9070,9073,9075,9078,9081,9084,9087,9090,9093,9096,9099,9102,9105,9108,9111,9114,9117,9120,9123,9126,9128,9131,9133,9136,9138,9140,9143,9145,9147,9150,9152,9155,9157,9160,9162,9165],{"class":156,"line":208},[154,9065,9066],{"class":348},"cmake",[154,9068,9069],{"class":228}," -D",[154,9071,9072],{"class":201}," CMAKE_BUILD_TYPE=RELEASE",[154,9074,9069],{"class":228},[154,9076,9077],{"class":201}," CMAKE_INSTALL_PREFIX=\u002Fhome\u002Fphoenix\u002Fusr\u002Flocal",[154,9079,9080],{"class":228},"  -DENABLE_SSE=ON",[154,9082,9083],{"class":228}," -DENABLE_SSE2=ON",[154,9085,9086],{"class":228}," -DENABLE_SSE3=ON",[154,9088,9089],{"class":228}," -DWITH_TBB=ON",[154,9091,9092],{"class":228}," -DWITH_1394=ON",[154,9094,9095],{"class":228}," -DWITH_V4L=ON",[154,9097,9098],{"class":228}," -DWITH_OPENGL=ON",[154,9100,9101],{"class":228},"  -DWITH_GTK=ON",[154,9103,9104],{"class":228}," -DWITH_JASPER=ON",[154,9106,9107],{"class":228}," -DWITH_JPEG=ON",[154,9109,9110],{"class":228}," -DWITH_PNG=ON",[154,9112,9113],{"class":228}," -DWITH_TIFF=ON",[154,9115,9116],{"class":228},"  -DWITH_OPENEXR=ON",[154,9118,9119],{"class":228}," -DWITH_PVAPI=ON",[154,9121,9122],{"class":228},"   -DWITH_EIGEN=ON",[154,9124,9125],{"class":228}," -DCMAKE_SKIP_RPATH=ON",[154,9127,9069],{"class":228},[154,9129,9130],{"class":201}," WITH_CUDA=ON",[154,9132,9069],{"class":228},[154,9134,9135],{"class":201}," ENABLE_FAST_MATH=",[154,9137,106],{"class":228},[154,9139,9069],{"class":228},[154,9141,9142],{"class":201}," CUDA_FAST_MATH=",[154,9144,106],{"class":228},[154,9146,9069],{"class":228},[154,9148,9149],{"class":201}," WITH_CUBLAS=",[154,9151,106],{"class":228},[154,9153,9154],{"class":228}," -DWITH_IPP=ON",[154,9156,9069],{"class":228},[154,9158,9159],{"class":201}," CUDA_GENERATION=Auto",[154,9161,9069],{"class":228},[154,9163,9164],{"class":201}," WITH_FFMPEG=ON",[154,9166,9167],{"class":201},"  ..\u002F\n",[13,9169,9170],{},"J'active lors de la compilation le maximum d'optimisation dont CUDA. J'active également FFMPEG sans lequel le nombre de fichier reconnu baisse énormément sur ma machine. Après\navoir lancé cmake j'obtiens le résultat suivant :",[145,9172,9177],{"className":9173,"code":9175,"language":9176},[9174],"language-text","-- General configuration for OpenCV 2.4.13.1 =====================================\n--   Version control:               2.4.13.1-48-gac118ae\n--\n--   Platform:\n--     Host:                        Linux 3.16.0-4-amd64 x86_64\n--     CMake:                       3.6.2\n--     CMake generator:             Unix Makefiles\n--     CMake build tool:            \u002Fusr\u002Fbin\u002Fmake\n--     Configuration:               RELEASE\n--\n--   C\u002FC++:\n--     Built as dynamic libs?:      YES\n--     C++ Compiler:                \u002Fusr\u002Fbin\u002Fc++  (ver 4.9.2)\n--     C++ flags (Release):         -fsigned-char -W -Wall -Werror=return-type -Werror=address -Werror=sequence-point -Wformat -Werror=format-security -Wmissing-declarations -Wundef -Winit-self -Wpointer-arith -Wshadow -Wsign-promo -Wno-narrowing -Wno-delete-non-virtual-dtor -Wno-comment -Wno-array-bounds -Wno-aggressive-loop-optimizations -fdiagnostics-show-option -Wno-long-long -pthread -fomit-frame-pointer -ffast-math -msse -msse2 -msse3 -ffunction-sections -O3 -DNDEBUG  -DNDEBUG\n--     C++ flags (Debug):           -fsigned-char -W -Wall -Werror=return-type -Werror=address -Werror=sequence-point -Wformat -Werror=format-security -Wmissing-declarations -Wundef -Winit-self -Wpointer-arith -Wshadow -Wsign-promo -Wno-narrowing -Wno-delete-non-virtual-dtor -Wno-comment -Wno-array-bounds -Wno-aggressive-loop-optimizations -fdiagnostics-show-option -Wno-long-long -pthread -fomit-frame-pointer -ffast-math -msse -msse2 -msse3 -ffunction-sections -g  -O0 -DDEBUG -D_DEBUG\n--     C Compiler:                  \u002Fusr\u002Fbin\u002Fcc\n--     C flags (Release):           -fsigned-char -W -Wall -Werror=return-type -Werror=address -Werror=sequence-point -Wformat -Werror=format-security -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wundef -Winit-self -Wpointer-arith -Wshadow -Wno-narrowing -Wno-comment -Wno-array-bounds -Wno-aggressive-loop-optimizations -fdiagnostics-show-option -Wno-long-long -pthread -fomit-frame-pointer -ffast-math -msse -msse2 -msse3 -ffunction-sections -O3 -DNDEBUG  -DNDEBUG\n--     C flags (Debug):             -fsigned-char -W -Wall -Werror=return-type -Werror=address -Werror=sequence-point -Wformat -Werror=format-security -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wundef -Winit-self -Wpointer-arith -Wshadow -Wno-narrowing -Wno-comment -Wno-array-bounds -Wno-aggressive-loop-optimizations -fdiagnostics-show-option -Wno-long-long -pthread -fomit-frame-pointer -ffast-math -msse -msse2 -msse3 -ffunction-sections -g  -O0 -DDEBUG -D_DEBUG\n--     Linker flags (Release):\n--     Linker flags (Debug):\n--     ccache:                      NO\n--     Precompiled headers:         YES\n--\n--   OpenCV modules:\n--     To be built:                 core flann imgproc highgui features2d calib3d ml video legacy objdetect photo gpu ocl nonfree contrib java python stitching superres ts videostab\n--     Disabled:                    world\n--     Disabled by dependency:      -\n--     Unavailable:                 androidcamera dynamicuda viz\n--\n--   GUI:\n--     QT:                          NO\n--     GTK+ 2.x:                    YES (ver 2.24.25)\n--     GThread :                    YES (ver 2.42.1)\n--     GtkGlExt:                    NO\n--     OpenGL support:              NO\n--     VTK support:                 NO\n--\n--   Media I\u002FO:\n--     ZLib:                        \u002Fusr\u002Flib\u002Fx86_64-linux-gnu\u002Flibz.so (ver 1.2.8)\n--     JPEG:                        \u002Fusr\u002Flib\u002Fx86_64-linux-gnu\u002Flibjpeg.so (ver )\n--     PNG:                         \u002Fusr\u002Flib\u002Fx86_64-linux-gnu\u002Flibpng.so (ver 1.2.50)\n--     TIFF:                        \u002Fusr\u002Flib\u002Fx86_64-linux-gnu\u002Flibtiff.so (ver 42 - 4.0.3)\n--     JPEG 2000:                   \u002Fusr\u002Flib\u002Fx86_64-linux-gnu\u002Flibjasper.so (ver 1.900.1)\n--     OpenEXR:                     \u002Fusr\u002Flib\u002Fx86_64-linux-gnu\u002FlibImath.so \u002Fusr\u002Flib\u002Fx86_64-linux-gnu\u002FlibIlmImf.so \u002Fusr\u002Flib\u002Fx86_64-linux-gnu\u002FlibIex.so \u002Fusr\u002Flib\u002Fx86_64-linux-gnu\u002FlibHalf.so \u002Fusr\u002Flib\u002Fx86_64-linux-gnu\u002FlibIlmThread.so (ver 1.6.1)\n--\n--   Video I\u002FO:\n--     DC1394 1.x:                  NO\n--     DC1394 2.x:                  YES (ver 2.2.3)\n--     FFMPEG:                      YES\n--       codec:                     YES (ver 56.1.0)\n--       format:                    YES (ver 56.1.0)\n--       util:                      YES (ver 54.3.0)\n--       swscale:                   YES (ver 3.0.0)\n--       resample:                  YES (ver 2.1.0)\n--       gentoo-style:              YES\n--     GStreamer:                   NO\n--     OpenNI:                      NO\n--     OpenNI PrimeSensor Modules:  NO\n--     PvAPI:                       NO\n--     GigEVisionSDK:               NO\n--     UniCap:                      NO\n--     UniCap ucil:                 NO\n--     V4L\u002FV4L2:                    NO\u002FYES\n--     XIMEA:                       NO\n--     Xine:                        NO\n--\n--   Other third-party libraries:\n--     Use IPP:                     IPP not found\n--     Use Eigen:                   NO\n--     Use TBB:                     NO\n--     Use OpenMP:                  NO\n--     Use GCD                      NO\n--     Use Concurrency              NO\n--     Use C=:                      NO\n--     Use Cuda:                    YES (ver 7.5)\n--     Use OpenCL:                  YES\n--\n--   NVIDIA CUDA\n--     Use CUFFT:                   YES\n--     Use CUBLAS:                  YES\n--     USE NVCUVID:                 NO\n--     NVIDIA GPU arch:             21\n--     NVIDIA PTX archs:\n--     Use fast math:               YES\n--     Tiny gpu module:             NO\n--\n--   OpenCL:\n--     Version:                     dynamic\n--     Include path:                \u002Fhome\u002Fphoenix\u002FDeveloppement\u002FExternalSoftware\u002Fopencv\u002F3rdparty\u002Finclude\u002Fopencl\u002F1.2\n--     Use AMD FFT:                 NO\n--     Use AMD BLAS:                NO\n--\n--   Python:\n--     Interpreter:                 \u002Fusr\u002Fbin\u002Fpython2 (ver 2.7.10)\n--     Libraries:                   \u002Fusr\u002Flib\u002Fx86_64-linux-gnu\u002Flibpython2.7.so (ver 2.7.10rc1)\n--     numpy:                       \u002Fusr\u002Flib\u002Fpython2.7\u002Fdist-packages\u002Fnumpy\u002Fcore\u002Finclude (ver 1.8.2)\n--     packages path:               lib\u002Fpython2.7\u002Fdist-packages\n--\n--   Java:\n--     ant:                         \u002Fusr\u002Fbin\u002Fant (ver 1.9.4)\n--     JNI:                         \u002Fusr\u002Flib\u002Fjvm\u002Fjava-7-openjdk-amd64\u002Finclude \u002Fusr\u002Flib\u002Fjvm\u002Fjava-7-openjdk-amd64\u002Finclude \u002Fusr\u002Flib\u002Fjvm\u002Fjava-7-openjdk-amd64\u002Finclude\n--     Java tests:                  YES\n--\n--   Documentation:\n--     Build Documentation:         NO\n--     Sphinx:                      NO\n--     PdfLaTeX compiler:           \u002Fusr\u002Fbin\u002Fpdflatex\n--     Doxygen:                     YES (\u002Fusr\u002Fbin\u002Fdoxygen)\n--\n--   Tests and samples:\n--     Tests:                       YES\n--     Performance tests:           YES\n--     C\u002FC++ Examples:              NO\n--\n--   Install path:                  \u002Fhome\u002Fphoenix\u002Fusr\u002Flocal\n--\n--   cvconfig.h is in:              \u002Fhome\u002Fphoenix\u002FDeveloppement\u002FExternalSoftware\u002Fopencv\u002Fbuild\n-- -----------------------------------------------------------------\n","text",[151,9178,9175],{"__ignoreMap":104},[13,9180,9181,9182,9185,9186,9189,9190,9193],{},"Pour que la compilation se déroule sans problème, il vous faudra installer certains paquets sur votre distribution. Sur Debian Jessie, j'ai installé ",[151,9183,9184],{},"nvidia-cuda-toolkit"," en\nversion ",[151,9187,9188],{},"7.5.18-4~bpo8+1",". Comme vous pouvez les voir c'est une version qui provient du repository de backports. La version ",[151,9191,9192],{},"6.0.37-5"," ne me permettait pas d'activer CUDA. J'ai\ndonc du monter l'ensemble du driver propriétaire sur mon poste de développement.",[13,9195,9196],{},"Rasssurez-vous, si vous ne voulez pas utiliser les backports ou ne pas utiliser de driver propriétaire, vous pouvez tester le programme dans sa version CPU. :)",[13,9198,9199],{},"Voici comment le code a été ré-écrit pour utiliser le GPU à la place du CPU:",[145,9201,9203],{"className":8599,"code":9202,"language":8601,"meta":104,"style":104},"struct BufferPSNR {                                    \u002F\u002F Optimized GPU versions\n    \u002F\u002F Data allocations are very expensive on GPU. Use a buffer to solve: allocate once reuse later.\n    cv::gpu::GpuMat gF1, gF2, gI1, gI2, gs, t1,t2;\n\n    cv::gpu::GpuMat buf;\n};\n\ndouble getPSNR_GPU_optimized(const cv::Mat& F1, const cv::Mat& F2, BufferPSNR& b) {\n    b.gF1.upload(F1);\n    b.gF2.upload(F2);\n\n    cv::gpu::resize(b.gF1, b.gI1, cv::Size(160, 120));\n    cv::gpu::resize(b.gF2, b.gI2, cv::Size(160, 120));\n\n    b.gI1.convertTo(b.t1, CV_32F);\n    b.gI2.convertTo(b.t2, CV_32F);\n\n    cv::gpu::absdiff(b.t1.reshape(1), b.t2.reshape(1), b.gs);\n    cv::gpu::multiply(b.gs, b.gs, b.gs);\n\n    double sse = cv::gpu::sum(b.gs, b.buf)[0];\n\n    if( sse \u003C= 1e-10) \u002F\u002F for small values return zero\n        return std::numeric_limits\u003Cdouble>::infinity();\n    else {\n        double mse = sse \u002F(double)(F1.channels() * F1.total());\n        double psnr = 10.0*log10((255*255)\u002Fmse);\n        return psnr;\n    }\n}\n",[151,9204,9205,9219,9224,9229,9233,9238,9243,9247,9290,9308,9324,9328,9373,9414,9418,9442,9465,9469,9524,9561,9565,9607,9611,9632,9650,9657,9695,9725,9731,9735],{"__ignoreMap":104},[154,9206,9207,9210,9213,9216],{"class":156,"line":157},[154,9208,9209],{"class":160},"struct",[154,9211,9212],{"class":164}," BufferPSNR",[154,9214,9215],{"class":174}," {",[154,9217,9218],{"class":647},"                                    \u002F\u002F Optimized GPU versions\n",[154,9220,9221],{"class":156,"line":178},[154,9222,9223],{"class":647},"    \u002F\u002F Data allocations are very expensive on GPU. Use a buffer to solve: allocate once reuse later.\n",[154,9225,9226],{"class":156,"line":208},[154,9227,9228],{"class":174},"    cv::gpu::GpuMat gF1, gF2, gI1, gI2, gs, t1,t2;\n",[154,9230,9231],{"class":156,"line":215},[154,9232,212],{"emptyLinePlaceholder":211},[154,9234,9235],{"class":156,"line":234},[154,9236,9237],{"class":174},"    cv::gpu::GpuMat buf;\n",[154,9239,9240],{"class":156,"line":256},[154,9241,9242],{"class":174},"};\n",[154,9244,9245],{"class":156,"line":374},[154,9246,212],{"emptyLinePlaceholder":211},[154,9248,9249,9251,9254,9256,9258,9260,9262,9264,9266,9268,9270,9272,9274,9276,9278,9280,9283,9285,9288],{"class":156,"line":402},[154,9250,8608],{"class":160},[154,9252,9253],{"class":348}," getPSNR_GPU_optimized",[154,9255,352],{"class":174},[154,9257,8616],{"class":160},[154,9259,8619],{"class":174},[154,9261,8622],{"class":164},[154,9263,8625],{"class":160},[154,9265,8628],{"class":358},[154,9267,2877],{"class":174},[154,9269,8616],{"class":160},[154,9271,8619],{"class":174},[154,9273,8622],{"class":164},[154,9275,8625],{"class":160},[154,9277,8641],{"class":358},[154,9279,2877],{"class":174},[154,9281,9282],{"class":164},"BufferPSNR",[154,9284,8625],{"class":160},[154,9286,9287],{"class":358}," b",[154,9289,815],{"class":174},[154,9291,9292,9295,9297,9300,9302,9305],{"class":156,"line":427},[154,9293,9294],{"class":164},"    b",[154,9296,298],{"class":174},[154,9298,9299],{"class":164},"gF1",[154,9301,298],{"class":174},[154,9303,9304],{"class":348},"upload",[154,9306,9307],{"class":174},"(F1);\n",[154,9309,9310,9312,9314,9317,9319,9321],{"class":156,"line":450},[154,9311,9294],{"class":164},[154,9313,298],{"class":174},[154,9315,9316],{"class":164},"gF2",[154,9318,298],{"class":174},[154,9320,9304],{"class":348},[154,9322,9323],{"class":174},"(F2);\n",[154,9325,9326],{"class":156,"line":458},[154,9327,212],{"emptyLinePlaceholder":211},[154,9329,9330,9332,9335,9338,9340,9342,9345,9347,9349,9351,9353,9355,9358,9361,9363,9365,9367,9369,9371],{"class":156,"line":463},[154,9331,8657],{"class":174},[154,9333,9334],{"class":164},"gpu",[154,9336,9337],{"class":174},"::",[154,9339,8660],{"class":348},[154,9341,352],{"class":174},[154,9343,9344],{"class":164},"b",[154,9346,298],{"class":174},[154,9348,9299],{"class":193},[154,9350,2877],{"class":174},[154,9352,9344],{"class":164},[154,9354,298],{"class":174},[154,9356,9357],{"class":193},"gI1",[154,9359,9360],{"class":174},", cv::",[154,9362,8666],{"class":348},[154,9364,352],{"class":174},[154,9366,8671],{"class":228},[154,9368,2877],{"class":174},[154,9370,8676],{"class":228},[154,9372,8679],{"class":174},[154,9374,9375,9377,9379,9381,9383,9385,9387,9389,9391,9393,9395,9397,9400,9402,9404,9406,9408,9410,9412],{"class":156,"line":485},[154,9376,8657],{"class":174},[154,9378,9334],{"class":164},[154,9380,9337],{"class":174},[154,9382,8660],{"class":348},[154,9384,352],{"class":174},[154,9386,9344],{"class":164},[154,9388,298],{"class":174},[154,9390,9316],{"class":193},[154,9392,2877],{"class":174},[154,9394,9344],{"class":164},[154,9396,298],{"class":174},[154,9398,9399],{"class":193},"gI2",[154,9401,9360],{"class":174},[154,9403,8666],{"class":348},[154,9405,352],{"class":174},[154,9407,8671],{"class":228},[154,9409,2877],{"class":174},[154,9411,8676],{"class":228},[154,9413,8679],{"class":174},[154,9415,9416],{"class":156,"line":499},[154,9417,212],{"emptyLinePlaceholder":211},[154,9419,9420,9422,9424,9426,9428,9430,9432,9434,9436,9439],{"class":156,"line":504},[154,9421,9294],{"class":164},[154,9423,298],{"class":174},[154,9425,9357],{"class":164},[154,9427,298],{"class":174},[154,9429,8732],{"class":348},[154,9431,352],{"class":174},[154,9433,9344],{"class":164},[154,9435,298],{"class":174},[154,9437,9438],{"class":193},"t1",[154,9440,9441],{"class":174},", CV_32F);\n",[154,9443,9444,9446,9448,9450,9452,9454,9456,9458,9460,9463],{"class":156,"line":509},[154,9445,9294],{"class":164},[154,9447,298],{"class":174},[154,9449,9399],{"class":164},[154,9451,298],{"class":174},[154,9453,8732],{"class":348},[154,9455,352],{"class":174},[154,9457,9344],{"class":164},[154,9459,298],{"class":174},[154,9461,9462],{"class":193},"t2",[154,9464,9441],{"class":174},[154,9466,9467],{"class":156,"line":526},[154,9468,212],{"emptyLinePlaceholder":211},[154,9470,9471,9473,9475,9477,9479,9481,9483,9485,9487,9489,9492,9494,9496,9499,9501,9503,9505,9507,9509,9511,9513,9515,9517,9519,9522],{"class":156,"line":549},[154,9472,8657],{"class":174},[154,9474,9334],{"class":164},[154,9476,9337],{"class":174},[154,9478,8716],{"class":348},[154,9480,352],{"class":174},[154,9482,9344],{"class":164},[154,9484,298],{"class":174},[154,9486,9438],{"class":164},[154,9488,298],{"class":174},[154,9490,9491],{"class":348},"reshape",[154,9493,352],{"class":174},[154,9495,106],{"class":228},[154,9497,9498],{"class":174},"), ",[154,9500,9344],{"class":164},[154,9502,298],{"class":174},[154,9504,9462],{"class":164},[154,9506,298],{"class":174},[154,9508,9491],{"class":348},[154,9510,352],{"class":174},[154,9512,106],{"class":228},[154,9514,9498],{"class":174},[154,9516,9344],{"class":164},[154,9518,298],{"class":174},[154,9520,9521],{"class":193},"gs",[154,9523,362],{"class":174},[154,9525,9526,9528,9530,9532,9535,9537,9539,9541,9543,9545,9547,9549,9551,9553,9555,9557,9559],{"class":156,"line":563},[154,9527,8657],{"class":174},[154,9529,9334],{"class":164},[154,9531,9337],{"class":174},[154,9533,9534],{"class":348},"multiply",[154,9536,352],{"class":174},[154,9538,9344],{"class":164},[154,9540,298],{"class":174},[154,9542,9521],{"class":193},[154,9544,2877],{"class":174},[154,9546,9344],{"class":164},[154,9548,298],{"class":174},[154,9550,9521],{"class":193},[154,9552,2877],{"class":174},[154,9554,9344],{"class":164},[154,9556,298],{"class":174},[154,9558,9521],{"class":193},[154,9560,362],{"class":174},[154,9562,9563],{"class":156,"line":569},[154,9564,212],{"emptyLinePlaceholder":211},[154,9566,9567,9569,9571,9573,9575,9577,9579,9582,9584,9586,9588,9590,9592,9594,9596,9599,9602,9604],{"class":156,"line":786},[154,9568,8787],{"class":160},[154,9570,8790],{"class":174},[154,9572,198],{"class":160},[154,9574,8619],{"class":174},[154,9576,9334],{"class":164},[154,9578,9337],{"class":174},[154,9580,9581],{"class":348},"sum",[154,9583,352],{"class":174},[154,9585,9344],{"class":164},[154,9587,298],{"class":174},[154,9589,9521],{"class":193},[154,9591,2877],{"class":174},[154,9593,9344],{"class":164},[154,9595,298],{"class":174},[154,9597,9598],{"class":193},"buf",[154,9600,9601],{"class":174},")[",[154,9603,926],{"class":228},[154,9605,9606],{"class":174},"];\n",[154,9608,9609],{"class":156,"line":797},[154,9610,212],{"emptyLinePlaceholder":211},[154,9612,9613,9615,9617,9619,9621,9623,9625,9627,9629],{"class":156,"line":818},[154,9614,8849],{"class":160},[154,9616,8852],{"class":174},[154,9618,8855],{"class":160},[154,9620,3559],{"class":228},[154,9622,1087],{"class":193},[154,9624,3230],{"class":174},[154,9626,6535],{"class":228},[154,9628,1076],{"class":174},[154,9630,9631],{"class":647}," \u002F\u002F for small values return zero\n",[154,9633,9634,9636,9638,9640,9642,9644,9646,9648],{"class":156,"line":838},[154,9635,1351],{"class":160},[154,9637,8876],{"class":174},[154,9639,8879],{"class":164},[154,9641,293],{"class":174},[154,9643,8608],{"class":160},[154,9645,8886],{"class":174},[154,9647,8889],{"class":348},[154,9649,610],{"class":174},[154,9651,9652,9655],{"class":156,"line":854},[154,9653,9654],{"class":160},"    else",[154,9656,175],{"class":174},[154,9658,9659,9661,9664,9666,9668,9670,9672,9674,9676,9679,9681,9683,9685,9687,9689,9691,9693],{"class":156,"line":882},[154,9660,8905],{"class":160},[154,9662,9663],{"class":174}," mse ",[154,9665,198],{"class":160},[154,9667,8790],{"class":174},[154,9669,8915],{"class":160},[154,9671,352],{"class":174},[154,9673,8608],{"class":160},[154,9675,8922],{"class":174},[154,9677,9678],{"class":164},"F1",[154,9680,298],{"class":174},[154,9682,8930],{"class":348},[154,9684,8933],{"class":174},[154,9686,4584],{"class":160},[154,9688,8628],{"class":164},[154,9690,298],{"class":174},[154,9692,8943],{"class":348},[154,9694,879],{"class":174},[154,9696,9697,9699,9701,9703,9705,9707,9710,9712,9714,9716,9718,9720,9722],{"class":156,"line":887},[154,9698,8905],{"class":160},[154,9700,8952],{"class":174},[154,9702,198],{"class":160},[154,9704,8957],{"class":228},[154,9706,4584],{"class":160},[154,9708,9709],{"class":348},"log10",[154,9711,8966],{"class":174},[154,9713,8969],{"class":228},[154,9715,4584],{"class":160},[154,9717,8969],{"class":228},[154,9719,1076],{"class":174},[154,9721,8915],{"class":160},[154,9723,9724],{"class":174},"mse);\n",[154,9726,9727,9729],{"class":156,"line":892},[154,9728,1351],{"class":160},[154,9730,8988],{"class":174},[154,9732,9733],{"class":156,"line":899},[154,9734,367],{"class":174},[154,9736,9737],{"class":156,"line":910},[154,9738,4329],{"class":174},[13,9740,9741,9742,9744,9745,9748],{},"L'utilisation de la structure ",[151,9743,9282],{}," permet de ne pas perdre de performance lors de l'initialisation relativement lourde des objets ",[151,9746,9747],{},"GpuMat",". Sans cela, l'utilisation du Gpu\nserait moins performant que la version Cpu.",[133,9750,9752],{"id":9751},"lexpérience","L'expérience",[13,9754,9755],{},"Maintenant place à l'expérience. Nous allons lancer notre programme sur notre jeu d'essai comprenant les vidéos issue des DVD, ainsi que les vidéos recompressées pour l'expérience.\nSi l'expérence se déroule bien l'algorithme devrait nous detecter les fichiers dupliqués, ainsi que les fichiers recompressés.",[64,9757,9759],{"id":9758},"lancement-et-selection-des-dossiers","Lancement et selection des dossiers",[13,9761,9762],{},"La première étape est la sélection des dossiers que l'on souhaite comparer. Le programme ira lire récursivement l'ensemble des dossiers pour y trouver l'ensemble des fichiers\nvidéos.",[13,9764,9765],{},"La sélection d'un projet provient de mon envie de départ de pouvoir enregistrer l'avancement du projet au fur et à mesure. Cette étape n'a pas été réalisée mais l'existance du mode\nprojet existe toujours.",[13,9767,9768],{},[123,9769],{"alt":9770,"src":9771},"Selection du projet","\u002FProgrammation\u002Ffind-similarity\u002Ffind-similarity-1.png",[13,9773,9774],{},"Une fois le dossier projet choisi, il faut sélectionner la liste des dossiers contenant les vidéos et lancer le programme ...",[64,9776,9778],{"id":9777},"comparaison-des-vidéos","Comparaison des vidéos",[13,9780,9781,9782,298],{},"Dans cette étape le programme compare l'ensemble des vidéos présentes dans les dossiers. L'ensemble du processus tourne dans des threads afin de ne pas figer l'IHM, grâce à l'API\n",[151,9783,9784],{},"QtConcurrent",[13,9786,9787,9791],{},[123,9788],{"alt":9789,"src":9790},"Recherche","\u002FProgrammation\u002Ffind-similarity\u002Ffind-similarity-2.png",[123,9792],{"alt":9793,"src":9794},"Recherche encore","\u002FProgrammation\u002Ffind-similarity\u002Ffind-similarity-3.png",[13,9796,9797],{},"Les étapes de la recherche sont donc :",[48,9799,9800,9803,9806,9809,9812],{},[51,9801,9802],{},"Constitution de la liste des fichiers",[51,9804,9805],{},"Récupération des méta-données",[51,9807,9808],{},"Création de la liste des paires de fichiers (en filtrant sur la durée)",[51,9810,9811],{},"Calcul du PSNR pour chaque paire de fichiers",[51,9813,9814],{},"Filtrage pour ne garder que les paires de fichiers dont le PSNR est supérieur à 30 db.",[13,9816,9817,9818,9820,9821,9823,9824,9827,9828,9831],{},"Lors de mon développement je me suis basé sur l'API ",[151,9819,9784],{}," pour faire les différentes étapes. Faisant beaucoup de développement NodeJS ces derniers temps je suis habitué à\nl'utilisation des promesses et de leur enchainement pour faire des processus complexes. J'ai trouvé dommage de ne pas retrouver la même chose dans l'API ",[151,9822,9784],{},". Pour\nreproduire un équivalent, lorsqu'un ",[151,9825,9826],{},"QFuture","se termine, le signal émis par ",[151,9829,9830],{},"QFutureWatcher"," est récupérer par un SLOT du moteur qui s'occupe de lancer l'étape suivante.",[64,9833,9835],{"id":9834},"la-page-de-résultat","La page de résultat",[13,9837,9838],{},"La page de résultat liste les vidéos considérées identiques suite à l'étude d'une des images. Un coup d'oeil visuel permet alors de se faire un avis sur la question, et de\nsupprimer la vidéo que l'on souhaite.",[13,9840,9841],{},[123,9842],{"alt":9843,"src":9844},"Résultat 1","\u002FProgrammation\u002Ffind-similarity\u002Ffind-similarity-4.png",[13,9846,9847],{},"Comme on peut le voir le programme retrouve les vidéos dont l'image est identique, ainsi que les films qui ont été redimensionnés sans trop de soucis.\nLe problème se situe alors au niveau du bruit qui est généré. Plusieurs films sont considérés comme proches alors que complètement différents. Pour régler ce problème, comparer\nplusieurs images d'une même vidéo à des timestamps différents pourrait peut-être régler le problème.",[13,9849,9850],{},[123,9851],{"alt":9852,"src":9853},"Résultat 2","\u002FProgrammation\u002Ffind-similarity\u002Ffind-similarity-5.png",[13,9855,9856],{},[141,9857,9858],{},"Je vous conseille de vérifier manuellement la qualité et la similarité de chaque vidéo manuellement avant toute suppression.",[133,9860,6958],{"id":6957},[13,9862,9863],{},"En conclusion, j'ai trouvé l'expérience intéressante, et maintenant qu'elle est terminée, je vais pouvoir en tenter une autre ;). Est-ce que le programme continuera d'évoluer ?\nPourquoi pas ? Cela dépendera des PR (Pull Request) et des demandes faites par les utilisateurs, ainsi que du temps que j'ai envie de passer dessus.",[4151,9865,9866],{},"html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html pre.shiki code .seHd6, html code.shiki .seHd6{--shiki-default:#C678DD}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sU0A5, html code.shiki .sU0A5{--shiki-default:#E5C07B}html pre.shiki code .s_ZVi, html code.shiki .s_ZVi{--shiki-default:#E06C75;--shiki-default-font-style:italic}html pre.shiki code .sV9Aq, html code.shiki .sV9Aq{--shiki-default:#7F848E;--shiki-default-font-style:italic}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}",{"title":104,"searchDepth":178,"depth":178,"links":9868},[9869,9870,9871,9872,9873,9874,9875,9876],{"id":8419,"depth":208,"text":8420},{"id":8446,"depth":208,"text":8447},{"id":8543,"depth":208,"text":8544},{"id":8995,"depth":208,"text":8996},{"id":9751,"depth":208,"text":9752},{"id":9758,"depth":178,"text":9759},{"id":9777,"depth":178,"text":9778},{"id":9834,"depth":178,"text":9835,"children":9877},[9878],{"id":6957,"depth":208,"text":6958},"2016-12-10",{"type":10,"value":9881},[9882,9884,9886,9888,9890,9892,9896],[133,9883,8420],{"id":8419},[13,9885,4216],{},[13,9887,8425],{},[13,9889,8428],{},[13,9891,8431],{},[13,9893,9894],{},[72,9895,8436],{},[13,9897,8439,9898,298],{},[23,9899,8442],{"href":8442,"rel":9900},[27],{"planet":211},"\u002Fpost\u002Ffindsimilarity",{"title":8414,"description":104},"findsimilarity","posts\u002FProgrammation\u002F2016-12-10-findsimilarity",[9907,9908,9909,9910],"qt","opencv","video","debian","LR-Ko6CoDmyd4rJj7msRVs_pkiaov-yO2_RYmY_C6cE",{"id":9913,"title":9914,"author":8,"body":9915,"category":7824,"categorySlug":8382,"date":10386,"description":7030,"excerpt":10387,"extension":4199,"location":4200,"meta":10423,"navigation":211,"path":10424,"published":211,"seo":10425,"slug":10426,"stem":10427,"tags":10428,"timeToRead":215,"__hash__":10429},"posts\u002Fposts\u002FPassprotect\u002F2016-10-02-presentation-passprotect.md","Passprotect 1.0.0",{"type":10,"value":9916,"toc":10377},[9917,9919,9922,9926,9933,9967,9970,9974,9981,9984,9994,9997,10003,10006,10020,10024,10027,10035,10038,10041,10045,10049,10052,10056,10064,10068,10108,10112,10119,10343,10346,10360,10363,10367,10374],[13,9918,7030],{},[13,9920,9921],{},"Passprotect est un nouvel outil permettant d'enregistrer et de gérer vos mots de passe. L'idée derrière Passprotect est de pouvoir enregistrer et lire les mots de passe de vos\nsites Internet, de vos cartes de crédit, ou de toute autre forme de texte que vous voudriez garder en sécurité.",[7043,9923,9925],{"id":9924},"la-concurence","La concurence",[13,9927,9928,9929,9932],{},"La première question que l'on peut se poser est ",[72,9930,9931],{},"Pourquoi ne pas avoir choisi une solution existante ?"," ? Alors passons en revue les différents outils que j'utilise actuellement\nou dont j'ai étudié la possibilité d'utiliser :",[48,9934,9935,9943,9951,9959],{},[51,9936,9937,9942],{},[23,9938,9941],{"href":9939,"rel":9940},"http:\u002F\u002Fwww.awallet.org\u002F",[27],"aWallet",": Un outil pour android que j'utilise actuellement (sans la partie synchro). Même si l'utilise jusqu'à présent régulièrement, le logiciel ne\nme convient pas. Il est propriétaire. Je n'ai pas confiance dans la synchro cloud. Je ne peux pas l'héberger moi même.",[51,9944,9945,9950],{},[23,9946,9949],{"href":9947,"rel":9948},"https:\u002F\u002Flastpass.com",[27],"LastPass",": propriétaire, trop compliqué,",[51,9952,9953,9958],{},[23,9954,9957],{"href":9955,"rel":9956},"http:\u002F\u002Fkeepass.info\u002F",[27],"KeePass",": Client lourd, je souhaitais un client léger avec potentiellement un futur client lourd sur android, et une extension chrome.",[51,9960,9961,9966],{},[23,9962,9965],{"href":9963,"rel":9964},"https:\u002F\u002Fspideroak.com\u002Fsolutions\u002Fencryptr",[27],"Encryptr",": Libre :) Belle interface :) Mais basée sur un serveur crypton distant. J'aurais bien sûr pu forker le projet et le faire\npointer sur un de mes serveurs, installé manuellement, mais non.",[13,9968,9969],{},"Une autre raison de vouloir faire ma version, est tout simplement pour m'amuser 😄.",[7043,9971,9973],{"id":9972},"linterface","L'interface",[13,9975,9976,9977,298],{},"Passprotect est un logiciel que vous pouvez installer sur votre propre serveur ou utiliser la version en ligne se trouvant à l'adresse\n",[23,9978,9979],{"href":9979,"rel":9980},"https:\u002F\u002Fpassprotect.shadoware.org",[27],[13,9982,9983],{},"Pour commencer nous avons la page de login qui vous permet de vous connecter, et si bien sûr vous n'avez pas de login, vous pouvez en enregistrer un. C'est à ce moment là que\nl'application va générer la clé et le sel utilisé pour protéger toutes vos données.",[13,9985,9986,9990],{},[123,9987],{"alt":9988,"src":9989},"Login","\u002FPassprotect\u002Flogin.png",[123,9991],{"alt":9992,"src":9993},"Register","\u002FPassprotect\u002Fregister.png",[13,9995,9996],{},"Une fois connecté, vous pouvez visualiser la liste de vos données cryptées. A ce moment les données ne sont pas cryptées.",[13,9998,9999],{},[123,10000],{"alt":10001,"src":10002},"Ma liste","\u002FPassprotect\u002Fliste.png",[13,10004,10005],{},"Et enfin vous pouvez visualiser le détail de vos données sauvegardées.",[13,10007,10008,10012,10016],{},[123,10009],{"alt":10010,"src":10011},"Détail du texte","\u002FPassprotect\u002Fdetail_text.png",[123,10013],{"alt":10014,"src":10015},"Détail du mot de passe","\u002FPassprotect\u002Fdetail_password.png",[123,10017],{"alt":10018,"src":10019},"Détail de la carte de paiement","\u002FPassprotect\u002Fdetail_card.png",[7043,10021,10023],{"id":10022},"comment-les-données-sont-cryptées","Comment les données sont cryptées",[13,10025,10026],{},"Lors de la création de l'utilisateur, l'application crée alors 2 clés :",[48,10028,10029,10032],{},[51,10030,10031],{},"La clé maître, utilisée pour chiffrer les mots de passe, cartes de crédit, et textes (à l'aide de la méthode AES-256-CTR) en tous genre. Cette clé est elle-même chiffrée avec\nle mot de passe de l'utilisateur et le sel (16 octets aléatoires). La clé maître est générée à partir de 32 octets choisis aléatoirement,",[51,10033,10034],{},"La clé de session utilisé pour chiffrer le jeton JWT contenant la clé maître lors de la session: Cette clé générée à partir de 32 octets choisis aléatoirement.",[13,10036,10037],{},"Lors de la sauvegarde d'une ligne, la clé maître est utilisée pour chiffrer les données, et inversement quand la clé est lue, la clé maître est utilisée pour les déchiffrer.",[13,10039,10040],{},"Seul l'utilisateur connecté a accès à ses propres données (chiffrées). Le chiffrage est fait côté serveur par le nodejs. Du coup les données transitant en clair vers le client, il\nfaut que le site soit accédé à partir d'un serveur en HTTPS.",[64,10042,10044],{"id":10043},"installation","Installation",[133,10046,10048],{"id":10047},"attention","Attention",[13,10050,10051],{},"Attention, j'essaie de rendre passprotect le plus sécurisé possible mais je ne suis pas un expert en cryptographie. Sauf si vous savez estimer le niveau de sécurité de l'application,\nl'utilisation de l'application pour protéger des données importantes se fait à vos risques et périls.",[133,10053,10055],{"id":10054},"les-pré-requis","Les pré-requis",[48,10057,10058,10061],{},[51,10059,10060],{},"NodeJS > 6.2.1",[51,10062,10063],{},"MongoDB > 3.2.7",[133,10065,10067],{"id":10066},"depuis-les-sources","Depuis les sources",[145,10069,10071],{"className":8456,"code":10070,"language":8458,"meta":104,"style":104},"    hg clone https:\u002F\u002Fgithub.com\u002Fphoenix741\u002Fpassprotect-server\n    npm install\n    MODE=prod npm run build\n",[151,10072,10073,10083,10091],{"__ignoreMap":104},[154,10074,10075,10078,10080],{"class":156,"line":157},[154,10076,10077],{"class":348},"    hg",[154,10079,9027],{"class":201},[154,10081,10082],{"class":201}," https:\u002F\u002Fgithub.com\u002Fphoenix741\u002Fpassprotect-server\n",[154,10084,10085,10088],{"class":156,"line":178},[154,10086,10087],{"class":348},"    npm",[154,10089,10090],{"class":201}," install\n",[154,10092,10093,10096,10098,10101,10104,10106],{"class":156,"line":208},[154,10094,10095],{"class":193},"    MODE",[154,10097,198],{"class":197},[154,10099,10100],{"class":201},"prod",[154,10102,10103],{"class":348}," npm",[154,10105,594],{"class":201},[154,10107,9055],{"class":201},[133,10109,10111],{"id":10110},"depuis-docker","Depuis docker",[13,10113,10114,10115,10118],{},"En utilisant ",[151,10116,10117],{},"docker-compose"," vous pouvez instancier les différentes images avec le fichier de configuration suivant. Pensez à modifier les clés servers.",[145,10120,10122],{"className":7982,"code":10121,"language":7984,"meta":104,"style":104},"version: \"2\"\nservices:\n  nodejs:\n    image: phoenix741\u002Fpassprotect-server:1.0.0\n    expose:\n      - 3000\n    links:\n      - mongodb\n    environment:\n      - MONGODB_HOST=mongodb:\u002F\u002Fmongodb:27017\u002Fpassprotect\n      - NODE_ENV=production\n      - DEBUG=App:*\n      - JWT_SECRET=dnLUMtULQsNmNbmGV3Lx8SxrxEtaxTc8aPdRh8YMemj515Faip7wQYueSaBFYm5r\n      - CRYPTO_SESSIONKEY=xtipKI38GUCvE5cNGtTJxa1wQFvCicF5GDLTWyaBAb5RQqQ8rRBR1yVEq7Jg10cu\n  nginx:\n    image: phoenix741\u002Fpassprotect-client:1.0.0\n    links:\n      - nodejs\n    environment:\n      - UPSTREAM_SERVER=nodejs\n      - UPSTREAM_PORT=3000\n      - PIWIK_SITE_URL=\u002F\u002Fstats-demo.shadoware.org\u002F\n      - PIWIK_SITE_ID=3\n    ports:\n      - \"8080:80\"\n  mongodb:\n    image: mongo:3.3.9\n    expose:\n      - 27017\n    volumes:\n      - \".\u002Fmongodb:\u002Fdata\"\n",[151,10123,10124,10133,10139,10146,10155,10162,10169,10176,10183,10189,10196,10203,10210,10217,10224,10231,10240,10246,10253,10259,10266,10273,10280,10287,10294,10301,10308,10317,10323,10330,10336],{"__ignoreMap":104},[154,10125,10126,10128,10130],{"class":156,"line":157},[154,10127,7991],{"class":193},[154,10129,4314],{"class":174},[154,10131,10132],{"class":201},"\"2\"\n",[154,10134,10135,10137],{"class":156,"line":178},[154,10136,8063],{"class":193},[154,10138,8008],{"class":174},[154,10140,10141,10144],{"class":156,"line":208},[154,10142,10143],{"class":193},"  nodejs",[154,10145,8008],{"class":174},[154,10147,10148,10150,10152],{"class":156,"line":215},[154,10149,8077],{"class":193},[154,10151,4314],{"class":174},[154,10153,10154],{"class":201},"phoenix741\u002Fpassprotect-server:1.0.0\n",[154,10156,10157,10160],{"class":156,"line":234},[154,10158,10159],{"class":193},"    expose",[154,10161,8008],{"class":174},[154,10163,10164,10166],{"class":156,"line":256},[154,10165,8094],{"class":174},[154,10167,10168],{"class":228},"3000\n",[154,10170,10171,10174],{"class":156,"line":374},[154,10172,10173],{"class":193},"    links",[154,10175,8008],{"class":174},[154,10177,10178,10180],{"class":156,"line":402},[154,10179,8094],{"class":174},[154,10181,10182],{"class":201},"mongodb\n",[154,10184,10185,10187],{"class":156,"line":427},[154,10186,8102],{"class":193},[154,10188,8008],{"class":174},[154,10190,10191,10193],{"class":156,"line":450},[154,10192,8094],{"class":174},[154,10194,10195],{"class":201},"MONGODB_HOST=mongodb:\u002F\u002Fmongodb:27017\u002Fpassprotect\n",[154,10197,10198,10200],{"class":156,"line":458},[154,10199,8094],{"class":174},[154,10201,10202],{"class":201},"NODE_ENV=production\n",[154,10204,10205,10207],{"class":156,"line":463},[154,10206,8094],{"class":174},[154,10208,10209],{"class":201},"DEBUG=App:*\n",[154,10211,10212,10214],{"class":156,"line":485},[154,10213,8094],{"class":174},[154,10215,10216],{"class":201},"JWT_SECRET=dnLUMtULQsNmNbmGV3Lx8SxrxEtaxTc8aPdRh8YMemj515Faip7wQYueSaBFYm5r\n",[154,10218,10219,10221],{"class":156,"line":499},[154,10220,8094],{"class":174},[154,10222,10223],{"class":201},"CRYPTO_SESSIONKEY=xtipKI38GUCvE5cNGtTJxa1wQFvCicF5GDLTWyaBAb5RQqQ8rRBR1yVEq7Jg10cu\n",[154,10225,10226,10229],{"class":156,"line":504},[154,10227,10228],{"class":193},"  nginx",[154,10230,8008],{"class":174},[154,10232,10233,10235,10237],{"class":156,"line":509},[154,10234,8077],{"class":193},[154,10236,4314],{"class":174},[154,10238,10239],{"class":201},"phoenix741\u002Fpassprotect-client:1.0.0\n",[154,10241,10242,10244],{"class":156,"line":526},[154,10243,10173],{"class":193},[154,10245,8008],{"class":174},[154,10247,10248,10250],{"class":156,"line":549},[154,10249,8094],{"class":174},[154,10251,10252],{"class":201},"nodejs\n",[154,10254,10255,10257],{"class":156,"line":563},[154,10256,8102],{"class":193},[154,10258,8008],{"class":174},[154,10260,10261,10263],{"class":156,"line":569},[154,10262,8094],{"class":174},[154,10264,10265],{"class":201},"UPSTREAM_SERVER=nodejs\n",[154,10267,10268,10270],{"class":156,"line":786},[154,10269,8094],{"class":174},[154,10271,10272],{"class":201},"UPSTREAM_PORT=3000\n",[154,10274,10275,10277],{"class":156,"line":797},[154,10276,8094],{"class":174},[154,10278,10279],{"class":201},"PIWIK_SITE_URL=\u002F\u002Fstats-demo.shadoware.org\u002F\n",[154,10281,10282,10284],{"class":156,"line":818},[154,10283,8094],{"class":174},[154,10285,10286],{"class":201},"PIWIK_SITE_ID=3\n",[154,10288,10289,10292],{"class":156,"line":838},[154,10290,10291],{"class":193},"    ports",[154,10293,8008],{"class":174},[154,10295,10296,10298],{"class":156,"line":854},[154,10297,8094],{"class":174},[154,10299,10300],{"class":201},"\"8080:80\"\n",[154,10302,10303,10306],{"class":156,"line":882},[154,10304,10305],{"class":193},"  mongodb",[154,10307,8008],{"class":174},[154,10309,10310,10312,10314],{"class":156,"line":887},[154,10311,8077],{"class":193},[154,10313,4314],{"class":174},[154,10315,10316],{"class":201},"mongo:3.3.9\n",[154,10318,10319,10321],{"class":156,"line":892},[154,10320,10159],{"class":193},[154,10322,8008],{"class":174},[154,10324,10325,10327],{"class":156,"line":899},[154,10326,8094],{"class":174},[154,10328,10329],{"class":228},"27017\n",[154,10331,10332,10334],{"class":156,"line":910},[154,10333,8087],{"class":193},[154,10335,8008],{"class":174},[154,10337,10338,10340],{"class":156,"line":921},[154,10339,8094],{"class":174},[154,10341,10342],{"class":201},"\".\u002Fmongodb:\u002Fdata\"\n",[13,10344,10345],{},"L'application est composée de deux images docker :",[48,10347,10348,10354],{},[51,10349,10350,10353],{},[151,10351,10352],{},"phoenix741\u002Fpassprotect-server:1.0.0",": contenant la partie serveur nodejs",[51,10355,10356,10359],{},[151,10357,10358],{},"phoenix741\u002Fpassprotect-client:1.0.0",": servant les fichiers static et redirigeant les appels à l'API vers le serveur.",[13,10361,10362],{},"La troisième image contient la base de données mongodb utilisée par le projet.",[64,10364,10366],{"id":10365},"utilisation","Utilisation",[13,10368,10369,10370,10373],{},"Pour accéder au serveur, utiliser l'adresse ",[151,10371,10372],{},"http:\u002F\u002Flocalhost:8080"," dans votre navigateur.",[4151,10375,10376],{},"html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}",{"title":104,"searchDepth":178,"depth":178,"links":10378},[10379,10385],{"id":10043,"depth":178,"text":10044,"children":10380},[10381,10382,10383,10384],{"id":10047,"depth":208,"text":10048},{"id":10054,"depth":208,"text":10055},{"id":10066,"depth":208,"text":10067},{"id":10110,"depth":208,"text":10111},{"id":10365,"depth":178,"text":10366},"2016-10-02",{"type":10,"value":10388},[10389,10391,10393,10395,10399,10421],[13,10390,7030],{},[13,10392,9921],{},[7043,10394,9925],{"id":9924},[13,10396,9928,10397,9932],{},[72,10398,9931],{},[48,10400,10401,10406,10411,10416],{},[51,10402,10403,9942],{},[23,10404,9941],{"href":9939,"rel":10405},[27],[51,10407,10408,9950],{},[23,10409,9949],{"href":9947,"rel":10410},[27],[51,10412,10413,9958],{},[23,10414,9957],{"href":9955,"rel":10415},[27],[51,10417,10418,9966],{},[23,10419,9965],{"href":9963,"rel":10420},[27],[13,10422,9969],{},{"planet":211},"\u002Fpost\u002Fpresentation-passprotect",{"title":9914,"description":7030},"presentation-passprotect","posts\u002FPassprotect\u002F2016-10-02-presentation-passprotect",[8382,6121,6122,8410],"CCmSZITdHfrTxlAN2-s6g_tkrpyHzhmvPrXpWcOyZzA",{"id":10431,"title":10432,"author":8,"body":10433,"category":11922,"categorySlug":11923,"date":11924,"description":104,"excerpt":11925,"extension":4199,"location":4200,"meta":11943,"navigation":211,"path":11944,"published":211,"seo":11945,"slug":11946,"stem":11947,"tags":11948,"timeToRead":485,"__hash__":11953},"posts\u002Fposts\u002FLogiciels\u002F2014-03-19-monlivretdemesse-fr.md","Mon livret de messe - Site de génération de livrets au format PDF",{"type":10,"value":10434,"toc":11902},[10435,10439,10441,10450,10458,10461,10499,10514,10518,10521,10524,10527,10534,10537,10540,10543,10546,10549,10552,10558,10561,10572,10575,10584,10588,10594,10598,10601,10611,10614,10617,10620,10623,10631,10634,10874,10877,11322,11325,11331,11335,11338,11341,11345,11348,11351,11355,11362,11366,11375,11379,11382,11386,11398,11401,11405,11408,11411,11415,11422,11425,11533,11536,11539,11547,11550,11554,11560,11563,11566,11577,11581,11584,11591,11594,11608,11612,11619,11627,11635,11638,11642,11648,11651,11654,11729,11732,11735,11738,11816,11819,11891,11893,11896,11899],[133,10436,10438],{"id":10437},"mon-livret-de-messe-générateur-de-pdf","Mon livret de messe - Générateur de PDF",[13,10440,4216],{},[13,10442,10443,10444,10449],{},"Ce petit billet pour vous parler d'un site que j'ai développé pour ma femme et dont l'adresse est\n",[23,10445,10448],{"href":10446,"rel":10447},"http:\u002F\u002Fmonlivretdemesse.fr\u002F",[27],"http:\u002F\u002Fmonlivretdemesse.fr",". Ce projet que je développe depuis\nplusieurs années, a été mis à jours récément. Je profite de cette mise à jours pour vous parler de\nce projet fonctionnellement mais aussi techniquement.",[13,10451,10452,10457],{},[23,10453,10456],{"href":10454,"rel":10455},"http:\u002F\u002Fmonlivretdemessse.fr",[27],"Monlivretdemesse.FR"," est un site permettant aux utilisateurs allant\nse marier de générer leur livret de messe au format PDF afin de l'imprimer directement chez eux.\nLes pages du livret ainsi générées sont alors ordonnées de telle manière qu'il suffit de faire une\nimpression recto\u002Fverso puis de plier les feuilles pour avoir son livret. Je me suis chargé\ndu développement de ce site, pendant que ma femme se charge du contenu (donc le contenu des textes,\ndes images, mais aussi et surtout le thème de chaque produit, leur format).",[13,10459,10460],{},"La nouvelle version sortie le 11 mars 2014 permet également la création d'autres types de produits\nafin de générer des",[48,10462,10463,10470,10482,10489,10496],{},[51,10464,10465],{},[23,10466,10469],{"href":10467,"rel":10468},"http:\u002F\u002Fmonlivretdemesse.fr\u002Ffr\u002Fcategory\u002Fbapteme",[27],"livrets de messe de baptêmes",[51,10471,10472,1484,10477],{},[23,10473,10476],{"href":10474,"rel":10475},"http:\u002F\u002Fmonlivretdemesse.fr\u002Ffr\u002Fcategory\u002Ffaire-part-mariage",[27],"faire-parts de mariage",[23,10478,10481],{"href":10479,"rel":10480},"http:\u002F\u002Fmonlivretdemesse.fr\u002Ffr\u002Fcategory\u002Ffaire-part-bapteme",[27],"de baptêmes",[51,10483,10484],{},[23,10485,10488],{"href":10486,"rel":10487},"http:\u002F\u002Fmonlivretdemesse.fr\u002Ffr\u002Fcategory\u002Fmenus-mariage",[27],"menus pour la soirée",[51,10490,10491],{},[23,10492,10495],{"href":10493,"rel":10494},"http:\u002F\u002Fmonlivretdemesse.fr\u002Ffr\u002Fcategory\u002Fmarque-places",[27],"des marques places",[51,10497,10498],{},"et bien d'autres...",[13,10500,10501,10502,10507,10508,10513],{},"Le site a été écrit à l'aide du framework Symfony2 et utilise la bibliothèque\n",[23,10503,10506],{"href":10504,"rel":10505},"http:\u002F\u002Fwww.tcpdf.org\u002F",[27],"TCPDF"," afin de générer les fichiers PDF. Les données sont stockées dans une\nbase de données ",[23,10509,10512],{"href":10510,"rel":10511},"http:\u002F\u002Fwww.mongodb.org\u002F",[27],"MongoDB",". Parmi les données on peut compter les données\nproduits, utilisateurs, mais aussi le cache des PDF générés enregistrés en tant que fichier dans\nGridFS (et ceci afin qu'un livret qui n'a pas été modifié ne soit pas re-généré).",[133,10515,10517],{"id":10516},"symfony2","Symfony2",[13,10519,10520],{},"Symfony 2 est un framework PHP permettant le développement de sites Internet. Il est livré par\ndéfaut avec un ORM: Doctrine qui permet de faire correspondre à une structure de base de données des\nclasses PHP qui seront automatiquement hydratées.",[13,10522,10523],{},"Le développement PHP s'en retrouve presque agréable (je préfère les langages compilés en règle\ngénérale). Le framework est de la même trempe que le Framework Python Django.",[13,10525,10526],{},"Ce dernier m'a d'ailleurs tenté (bien qu'interpreté aussi), mais une lecture rapide de la\ndocumentation m'a donné l'impression d'être un peu moins pratique à utiliser que Symfony2. Peut-être\ncar je ne fais pas de Python.",[13,10528,10529,10530,10533],{},"Pour mon prochain projet j'étudierai l'utilisation de symfony2 vs ruby on rails vs django vs\nnode.js. Mais je ne suis pas sûr qu'au final l'utilisation d'une des technos précédentes m'apporte\nbeaucoup plus par rapport à ce que sait déjà faire un framework comme symfony2. L'avantage d'un tel\nframework réside aussi dans le nombre de ",[72,10531,10532],{},"bundle"," et de librairie utilisables à l'extérieur.",[13,10535,10536],{},"Par rapport à la distribution de base de symfony2 j'ai remplacé la version Doctrine ORM par Doctrine\nODM. Ce qui me permet de me connecter à une base de données MongoDB.",[133,10538,10512],{"id":10539},"mongodb",[13,10541,10542],{},"Mon projet d'abord basé sur une base MySQL a été basculé sur une base de données NoSQL nommée\nMongoDB.",[13,10544,10545],{},"La raison n'est pas technique (je n'ai pas besoin de replication, de sharding, ...., pas assez de\nvisiteurs). J'avais juste envie de tester cette base de données sur mon projet. De plus l'aspect\norienté document est agréable au développement.",[13,10547,10548],{},"En effet, au lieu de stocker les informations dans différentes tables et de tenter d'y accéder à\nl'aide de jointure ou de requête multiple, dans on MongoDB on stocke un document dans un format\nbinaire du JSON (le BSON).",[13,10550,10551],{},"Un panier stocké en base pourra avoir la forme :",[145,10553,10556],{"className":10554,"code":10555,"language":9176},[9174],"{\n  \"_id\": ObjectId(\"......................\"),\n  \"expiration_updated_at\": ISODate(\"2014-03-17T21:00:34.0Z\"),\n  \"lines\": [\n    {\n      \"product\": {\n        \"product\": \"marque-place-baroque-dore\",\n        \"product_name\": \"Marque-place baroque doré\",\n        \"variant\": \"dore-gris\",\n        \"variant_name\": \"Doré\\\u002FGris\",\n        \"amount\": 7.9\n      },\n      \"custom\": ObjectId(\"....................\")\n    }\n  ],\n  \"lines_count\": NumberInt(1)\n}\n",[151,10557,10555],{"__ignoreMap":104},[13,10559,10560],{},"On retrouve dans un seul document les informations liées au panier et les informations concernant\nchaque ligne. La dénormalisation n'étant pas un problème, on rappelera alors ici le nom du produit,\net le nombre de lignes que l'on pourrait retrouver autrement mais qui permettra un affichage plus\nrapide de cette manière.",[13,10562,10563,10564,10567,10568,10571],{},"Par exemple, sur la page d'acceuil où on affiche le nombre de produit dans le panier, il nous\nsuffira de requêter ",[72,10565,10566],{},"lines_count"," sans toucher à l'attribute ",[72,10569,10570],{},"lines",". Ensuite lors de l'affichage\ndu panier, on récupérera les lignes et les informations du panier en une seule requête et sans\njointure.",[13,10573,10574],{},"Ainsi pour des entités avec forte relation, qu'on ne récupère jamais les unes sans les autres, une\nseule requête permet de récupérer toutes les informations. Par exemple, dans une base relationnelle,\non aura l'habitude de stocker l'entête du panier dans une table, et les lignes dans une autre table.\nAvec Mongo, si je veux récupérer le panier en une requête, je récupère aussi les lignes. Cela\nimplique par contre de ne pouvoir requêter facilement sur les lignes du panier indépendamment de\nleur entête.",[13,10576,10577,10578,10583],{},"Le seul problème que j'ai actuellement avec l'ODM Doctrine est que la mise à jour du panier se fait\nen plusieurs requêtes alors qu'il serait préférable de le faire en une seule pour des questions\nd'atomicité. Ce point fait d'ailleurs l'objet du ticket\n",[23,10579,10582],{"href":10580,"rel":10581},"https:\u002F\u002Fgithub.com\u002Fdoctrine\u002Fmongodb-odm\u002Fissues\u002F437",[27],"437"," du Github du projet.",[133,10585,10587],{"id":10586},"les-différents-modules-du-site","Les différents modules du site",[13,10589,10590,10591,10593],{},"Dans cette section je vais vous parler rapidement des différents ",[72,10592,10532],{}," que j'ai créé pour le site.",[64,10595,10597],{"id":10596},"le-cms","Le CMS",[13,10599,10600],{},"Afin de faciliter l'édition des pages de contenu (Mention légales, Documentation, FAQ) sans toucher\nau code. J'ai écrit un mini CMS. Le but est de stocker dans la base de données les différentes pages\ndu projet, ainsi que les images associées aux pages.",[13,10602,10603,10604,10606,10607,10610],{},"Pour cela j'ai un ",[72,10605,10532],{}," nommé CMS qui utilise la notion d'édition inline de CKEditor. J'ai écris\nmon propre ",[141,10608,10609],{},"Explorer de media"," qui permet de récupérer et ajouter dans cette même base des\nfichiers à attacher aux différentes pages.",[13,10612,10613],{},"L'adresse de la page est alors décomposée pour récupérer la clé de la page. Une page dont l'adresse\nsera \u002Fpage\u002Fprout aura comme clé en base \u002Fprout. Pour un utilisateur non administrateur (ou anonyme),\nsi la page est trouvée, elle est affichée telle quelle et si elle n'est pas trouvée, une page 404\nest affichée.",[13,10615,10616],{},"En mode administrateur si la page existe, elle est ouverte en mode édition inline pour CKEditor. Si\nla page n'existe pas, elle est ouverte en mode création et en mode inline avec CKEditor. Il est\nainsi super facile pour un administrateur de créer de nouvelles pages.",[13,10618,10619],{},"La partie media permet d'ajouter dans un GridFS dedié les images, et fichiers attachés.",[13,10621,10622],{},"Le module est du coup assez simple, deux documents",[48,10624,10625,10628],{},[51,10626,10627],{},"Page",[51,10629,10630],{},"Media",[13,10632,10633],{},"Le document Page ressemble à ceci:",[145,10635,10639],{"className":10636,"code":10637,"language":10638,"meta":104,"style":104},"language-php shiki shiki-themes one-dark-pro","\u002F**\n  * Page\n  *\n  * @MongoDB\\Document(collection=\"cms_page\")\n  *\u002F\nclass Page {\n    \u002F**\n      * @var string\n      *\n      * @MongoDB\\Id(strategy=\"CUSTOM\", options={\"class\"=\"\\Shadoware\\CMSBundle\\Generator\\PageSlugGenerator\"})\n      *\u002F\n    private $slug;\n\n    \u002F**\n      * @var string\n      *\n      * @MongoDB\\String\n      *\u002F\n    private $name;\n\n    \u002F**\n      * @var string\n      *\n      * @MongoDB\\String\n      *\u002F\n    private $title;\n\n    \u002F**\n      * @var string\n      *\n      * @MongoDB\\String\n      *\u002F\n    private $content;\n\n    \u002F**\n      * @var string $lang\n      *\n      * @MongoDB\\String\n      *\u002F\n    private $lang;\n}\n","php",[151,10640,10641,10646,10651,10656,10661,10666,10675,10680,10693,10698,10703,10708,10717,10721,10725,10733,10737,10742,10746,10755,10759,10763,10771,10775,10779,10783,10792,10796,10800,10808,10812,10816,10820,10829,10833,10837,10849,10853,10857,10861,10870],{"__ignoreMap":104},[154,10642,10643],{"class":156,"line":157},[154,10644,10645],{"class":647},"\u002F**\n",[154,10647,10648],{"class":156,"line":178},[154,10649,10650],{"class":647},"  * Page\n",[154,10652,10653],{"class":156,"line":208},[154,10654,10655],{"class":647},"  *\n",[154,10657,10658],{"class":156,"line":215},[154,10659,10660],{"class":647},"  * @MongoDB\\Document(collection=\"cms_page\")\n",[154,10662,10663],{"class":156,"line":234},[154,10664,10665],{"class":647},"  *\u002F\n",[154,10667,10668,10670,10673],{"class":156,"line":256},[154,10669,161],{"class":160},[154,10671,10672],{"class":164}," Page",[154,10674,175],{"class":174},[154,10676,10677],{"class":156,"line":374},[154,10678,10679],{"class":647},"    \u002F**\n",[154,10681,10682,10685,10689],{"class":156,"line":402},[154,10683,10684],{"class":647},"      * ",[154,10686,10688],{"class":10687},"shdRp","@var",[154,10690,10692],{"class":10691},"sKU4T"," string\n",[154,10694,10695],{"class":156,"line":427},[154,10696,10697],{"class":647},"      *\n",[154,10699,10700],{"class":156,"line":450},[154,10701,10702],{"class":647},"      * @MongoDB\\Id(strategy=\"CUSTOM\", options={\"class\"=\"\\Shadoware\\CMSBundle\\Generator\\PageSlugGenerator\"})\n",[154,10704,10705],{"class":156,"line":458},[154,10706,10707],{"class":647},"      *\u002F\n",[154,10709,10710,10712,10715],{"class":156,"line":463},[154,10711,181],{"class":160},[154,10713,10714],{"class":193}," $slug",[154,10716,205],{"class":174},[154,10718,10719],{"class":156,"line":485},[154,10720,212],{"emptyLinePlaceholder":211},[154,10722,10723],{"class":156,"line":499},[154,10724,10679],{"class":647},[154,10726,10727,10729,10731],{"class":156,"line":504},[154,10728,10684],{"class":647},[154,10730,10688],{"class":10687},[154,10732,10692],{"class":10691},[154,10734,10735],{"class":156,"line":509},[154,10736,10697],{"class":647},[154,10738,10739],{"class":156,"line":526},[154,10740,10741],{"class":647},"      * @MongoDB\\String\n",[154,10743,10744],{"class":156,"line":549},[154,10745,10707],{"class":647},[154,10747,10748,10750,10753],{"class":156,"line":563},[154,10749,181],{"class":160},[154,10751,10752],{"class":193}," $name",[154,10754,205],{"class":174},[154,10756,10757],{"class":156,"line":569},[154,10758,212],{"emptyLinePlaceholder":211},[154,10760,10761],{"class":156,"line":786},[154,10762,10679],{"class":647},[154,10764,10765,10767,10769],{"class":156,"line":797},[154,10766,10684],{"class":647},[154,10768,10688],{"class":10687},[154,10770,10692],{"class":10691},[154,10772,10773],{"class":156,"line":818},[154,10774,10697],{"class":647},[154,10776,10777],{"class":156,"line":838},[154,10778,10741],{"class":647},[154,10780,10781],{"class":156,"line":854},[154,10782,10707],{"class":647},[154,10784,10785,10787,10790],{"class":156,"line":882},[154,10786,181],{"class":160},[154,10788,10789],{"class":193}," $title",[154,10791,205],{"class":174},[154,10793,10794],{"class":156,"line":887},[154,10795,212],{"emptyLinePlaceholder":211},[154,10797,10798],{"class":156,"line":892},[154,10799,10679],{"class":647},[154,10801,10802,10804,10806],{"class":156,"line":899},[154,10803,10684],{"class":647},[154,10805,10688],{"class":10687},[154,10807,10692],{"class":10691},[154,10809,10810],{"class":156,"line":910},[154,10811,10697],{"class":647},[154,10813,10814],{"class":156,"line":921},[154,10815,10741],{"class":647},[154,10817,10818],{"class":156,"line":931},[154,10819,10707],{"class":647},[154,10821,10822,10824,10827],{"class":156,"line":946},[154,10823,181],{"class":160},[154,10825,10826],{"class":193}," $content",[154,10828,205],{"class":174},[154,10830,10831],{"class":156,"line":961},[154,10832,212],{"emptyLinePlaceholder":211},[154,10834,10835],{"class":156,"line":976},[154,10836,10679],{"class":647},[154,10838,10839,10841,10843,10846],{"class":156,"line":1000},[154,10840,10684],{"class":647},[154,10842,10688],{"class":10687},[154,10844,10845],{"class":10691}," string",[154,10847,10848],{"class":647}," $lang\n",[154,10850,10851],{"class":156,"line":1005},[154,10852,10697],{"class":647},[154,10854,10855],{"class":156,"line":5920},[154,10856,10741],{"class":647},[154,10858,10859],{"class":156,"line":5926},[154,10860,10707],{"class":647},[154,10862,10863,10865,10868],{"class":156,"line":8267},[154,10864,181],{"class":160},[154,10866,10867],{"class":193}," $lang",[154,10869,205],{"class":174},[154,10871,10872],{"class":156,"line":8275},[154,10873,4329],{"class":174},[13,10875,10876],{},"Le controlleur est assez simple :",[145,10878,10880],{"className":10636,"code":10879,"language":10638,"meta":104,"style":104},"\u002F**\n  * @Route(\"\u002Fpage\u002F{slug}\", requirements={\"slug\" = \".+\"})\n  * @Template()\n  *\u002F\npublic function indexAction($slug) {\n    $dm = $this->get('doctrine_mongodb')->getManager();\n\n    $page = $dm->getRepository(\"CMSBundle:Page\")->find($slug);\n    if ($page == null) {\n        if ($this->get('security.context')->isGranted('ROLE_ADMIN')) {\n            $page = new Page();\n            $page->setTitle(\"Titre de la page\");\n            $page->setSlug($slug);\n            $page->setName(\"Nom\");\n            $page->setContent(\"Contenue de la page\");\n        } else {\n            throw $this->createNotFoundException(\"Page not found\");\n        }\n    }\n\n    $request = $this->getRequest();\n    if ($request->getMethod() == \"POST\" && $page != null) {\n        $page->setName($request->get('name'));\n        $page->setTitle($request->get('title'));\n        $page->setContent($request->get('content'));\n\n        $dm->persist($page);\n        $dm->flush();\n    }\n\n    return array('page' => $page);\n}\n",[151,10881,10882,10886,10891,10896,10900,10917,10944,10948,10979,10995,11025,11038,11054,11069,11085,11101,11109,11128,11132,11136,11140,11156,11189,11213,11236,11259,11263,11279,11290,11294,11298,11318],{"__ignoreMap":104},[154,10883,10884],{"class":156,"line":157},[154,10885,10645],{"class":647},[154,10887,10888],{"class":156,"line":178},[154,10889,10890],{"class":647},"  * @Route(\"\u002Fpage\u002F{slug}\", requirements={\"slug\" = \".+\"})\n",[154,10892,10893],{"class":156,"line":208},[154,10894,10895],{"class":647},"  * @Template()\n",[154,10897,10898],{"class":156,"line":215},[154,10899,10665],{"class":647},[154,10901,10902,10904,10907,10910,10912,10915],{"class":156,"line":234},[154,10903,6165],{"class":160},[154,10905,10906],{"class":160}," function",[154,10908,10909],{"class":348}," indexAction",[154,10911,352],{"class":174},[154,10913,10914],{"class":193},"$slug",[154,10916,815],{"class":174},[154,10918,10919,10922,10924,10927,10929,10931,10933,10936,10939,10942],{"class":156,"line":256},[154,10920,10921],{"class":193},"    $dm",[154,10923,2968],{"class":197},[154,10925,10926],{"class":164}," $this",[154,10928,1535],{"class":174},[154,10930,2293],{"class":348},[154,10932,352],{"class":174},[154,10934,10935],{"class":201},"'doctrine_mongodb'",[154,10937,10938],{"class":174},")->",[154,10940,10941],{"class":348},"getManager",[154,10943,610],{"class":174},[154,10945,10946],{"class":156,"line":374},[154,10947,212],{"emptyLinePlaceholder":211},[154,10949,10950,10953,10955,10958,10960,10963,10965,10968,10970,10973,10975,10977],{"class":156,"line":402},[154,10951,10952],{"class":193},"    $page",[154,10954,2968],{"class":197},[154,10956,10957],{"class":193}," $dm",[154,10959,1535],{"class":174},[154,10961,10962],{"class":348},"getRepository",[154,10964,352],{"class":174},[154,10966,10967],{"class":201},"\"CMSBundle:Page\"",[154,10969,10938],{"class":174},[154,10971,10972],{"class":348},"find",[154,10974,352],{"class":174},[154,10976,10914],{"class":193},[154,10978,362],{"class":174},[154,10980,10981,10983,10985,10988,10991,10993],{"class":156,"line":427},[154,10982,8849],{"class":160},[154,10984,806],{"class":174},[154,10986,10987],{"class":193},"$page",[154,10989,10990],{"class":197}," ==",[154,10992,229],{"class":228},[154,10994,815],{"class":174},[154,10996,10997,10999,11001,11004,11006,11008,11010,11013,11015,11018,11020,11023],{"class":156,"line":450},[154,10998,2455],{"class":160},[154,11000,806],{"class":174},[154,11002,11003],{"class":164},"$this",[154,11005,1535],{"class":174},[154,11007,2293],{"class":348},[154,11009,352],{"class":174},[154,11011,11012],{"class":201},"'security.context'",[154,11014,10938],{"class":174},[154,11016,11017],{"class":348},"isGranted",[154,11019,352],{"class":174},[154,11021,11022],{"class":201},"'ROLE_ADMIN'",[154,11024,1561],{"class":174},[154,11026,11027,11030,11032,11034,11036],{"class":156,"line":458},[154,11028,11029],{"class":193},"            $page",[154,11031,2968],{"class":197},[154,11033,312],{"class":160},[154,11035,10672],{"class":164},[154,11037,610],{"class":174},[154,11039,11040,11042,11044,11047,11049,11052],{"class":156,"line":463},[154,11041,11029],{"class":193},[154,11043,1535],{"class":174},[154,11045,11046],{"class":348},"setTitle",[154,11048,352],{"class":174},[154,11050,11051],{"class":201},"\"Titre de la page\"",[154,11053,362],{"class":174},[154,11055,11056,11058,11060,11063,11065,11067],{"class":156,"line":485},[154,11057,11029],{"class":193},[154,11059,1535],{"class":174},[154,11061,11062],{"class":348},"setSlug",[154,11064,352],{"class":174},[154,11066,10914],{"class":193},[154,11068,362],{"class":174},[154,11070,11071,11073,11075,11078,11080,11083],{"class":156,"line":499},[154,11072,11029],{"class":193},[154,11074,1535],{"class":174},[154,11076,11077],{"class":348},"setName",[154,11079,352],{"class":174},[154,11081,11082],{"class":201},"\"Nom\"",[154,11084,362],{"class":174},[154,11086,11087,11089,11091,11094,11096,11099],{"class":156,"line":504},[154,11088,11029],{"class":193},[154,11090,1535],{"class":174},[154,11092,11093],{"class":348},"setContent",[154,11095,352],{"class":174},[154,11097,11098],{"class":201},"\"Contenue de la page\"",[154,11100,362],{"class":174},[154,11102,11103,11105,11107],{"class":156,"line":509},[154,11104,800],{"class":174},[154,11106,1975],{"class":160},[154,11108,175],{"class":174},[154,11110,11111,11114,11116,11118,11121,11123,11126],{"class":156,"line":526},[154,11112,11113],{"class":160},"            throw",[154,11115,10926],{"class":164},[154,11117,1535],{"class":174},[154,11119,11120],{"class":348},"createNotFoundException",[154,11122,352],{"class":174},[154,11124,11125],{"class":201},"\"Page not found\"",[154,11127,362],{"class":174},[154,11129,11130],{"class":156,"line":549},[154,11131,566],{"class":174},[154,11133,11134],{"class":156,"line":563},[154,11135,367],{"class":174},[154,11137,11138],{"class":156,"line":569},[154,11139,212],{"emptyLinePlaceholder":211},[154,11141,11142,11145,11147,11149,11151,11154],{"class":156,"line":786},[154,11143,11144],{"class":193},"    $request",[154,11146,2968],{"class":197},[154,11148,10926],{"class":164},[154,11150,1535],{"class":174},[154,11152,11153],{"class":348},"getRequest",[154,11155,610],{"class":174},[154,11157,11158,11160,11162,11165,11167,11170,11172,11175,11178,11180,11183,11185,11187],{"class":156,"line":797},[154,11159,8849],{"class":160},[154,11161,806],{"class":174},[154,11163,11164],{"class":193},"$request",[154,11166,1535],{"class":174},[154,11168,11169],{"class":348},"getMethod",[154,11171,8933],{"class":174},[154,11173,11174],{"class":197},"==",[154,11176,11177],{"class":201}," \"POST\"",[154,11179,1905],{"class":197},[154,11181,11182],{"class":193}," $page",[154,11184,1900],{"class":197},[154,11186,229],{"class":228},[154,11188,815],{"class":174},[154,11190,11191,11194,11196,11198,11200,11202,11204,11206,11208,11211],{"class":156,"line":818},[154,11192,11193],{"class":193},"        $page",[154,11195,1535],{"class":174},[154,11197,11077],{"class":348},[154,11199,352],{"class":174},[154,11201,11164],{"class":193},[154,11203,1535],{"class":174},[154,11205,2293],{"class":348},[154,11207,352],{"class":174},[154,11209,11210],{"class":201},"'name'",[154,11212,8679],{"class":174},[154,11214,11215,11217,11219,11221,11223,11225,11227,11229,11231,11234],{"class":156,"line":838},[154,11216,11193],{"class":193},[154,11218,1535],{"class":174},[154,11220,11046],{"class":348},[154,11222,352],{"class":174},[154,11224,11164],{"class":193},[154,11226,1535],{"class":174},[154,11228,2293],{"class":348},[154,11230,352],{"class":174},[154,11232,11233],{"class":201},"'title'",[154,11235,8679],{"class":174},[154,11237,11238,11240,11242,11244,11246,11248,11250,11252,11254,11257],{"class":156,"line":854},[154,11239,11193],{"class":193},[154,11241,1535],{"class":174},[154,11243,11093],{"class":348},[154,11245,352],{"class":174},[154,11247,11164],{"class":193},[154,11249,1535],{"class":174},[154,11251,2293],{"class":348},[154,11253,352],{"class":174},[154,11255,11256],{"class":201},"'content'",[154,11258,8679],{"class":174},[154,11260,11261],{"class":156,"line":882},[154,11262,212],{"emptyLinePlaceholder":211},[154,11264,11265,11268,11270,11273,11275,11277],{"class":156,"line":887},[154,11266,11267],{"class":193},"        $dm",[154,11269,1535],{"class":174},[154,11271,11272],{"class":348},"persist",[154,11274,352],{"class":174},[154,11276,10987],{"class":193},[154,11278,362],{"class":174},[154,11280,11281,11283,11285,11288],{"class":156,"line":892},[154,11282,11267],{"class":193},[154,11284,1535],{"class":174},[154,11286,11287],{"class":348},"flush",[154,11289,610],{"class":174},[154,11291,11292],{"class":156,"line":899},[154,11293,367],{"class":174},[154,11295,11296],{"class":156,"line":910},[154,11297,212],{"emptyLinePlaceholder":211},[154,11299,11300,11303,11306,11308,11311,11314,11316],{"class":156,"line":921},[154,11301,11302],{"class":160},"    return",[154,11304,11305],{"class":197}," array",[154,11307,352],{"class":174},[154,11309,11310],{"class":201},"'page'",[154,11312,11313],{"class":174}," => ",[154,11315,10987],{"class":193},[154,11317,362],{"class":174},[154,11319,11320],{"class":156,"line":931},[154,11321,4329],{"class":174},[13,11323,11324],{},"Et enfin la vue Twig contient les références à CKEditor et l'activation du contenu modifiable inliné\npour les projets. Pour l'instant le module est fortement lié à l'application existante. J'ai dans\nl'espoir d'avoir assez de temps un jour pour externaliser ce module afin de pouvoir le partager à\nplus de monde.",[13,11326,11327,11328,11330],{},"Si vous avez envie d'avoir plus d'information sur ce ",[72,11329,10532],{}," n'hesitez pas à me contacter et je\nvous réponderez avec joie.",[64,11332,11334],{"id":11333},"les-produits","Les produits",[13,11336,11337],{},"La partie produits et beaucoup plus liée à l'activité du site. Les produits contiennent pour chaque\ncolori, un modèle dans lequel sont définis la position des différentes informations qui seront par\nla suite saisies par l'utilisateur. Ma femme peut ainsi, après avoir préparé son modèle le\npersonnaliser sur le site.",[13,11339,11340],{},"Un document de type produit personnalisé permet ensuite de spécialiser le produit en y ajoutant les\ninformations saisies par l'utilisateur. C'est ce produit final qui est ensuite acheté par\nl'utilisateur.",[64,11342,11344],{"id":11343},"les-paiements","Les paiements",[13,11346,11347],{},"La partie paiement contient la gestion du panier, de la facturation et le lien avec le site Paypal.\nEn effet on utilise l'API de paypal pour effectuer les paiements car nous n'avions pas l'envie de\ngérer pour ce site un système de paiement onéreux et complexe. Paypal prélève un pourcentage de la\ntransaction, et permet le paiement par carte bancaire des utilisateurs anonymes, ce qui nous\nconvient.",[13,11349,11350],{},"Au niveau du panier, certaines informations sont enregistrées, comme le nombre de lignes dans le\npanier, ainsi que le nom du produit et le nom du coloris choisis afin de faciliter le requêtage et\nl'affichage.",[64,11352,11354],{"id":11353},"la-gestion-des-utilisateurs","La gestion des utilisateurs",[13,11356,11357,11358,11361],{},"La gestion des utilisateurs passent par ",[72,11359,11360],{},"FOS\u002FUserBundle",". Quelques personnalisations ont été\najoutées à ce module afin de correspondre au thème du site et aussi pour ajouter quelques\ninformations.",[64,11363,11365],{"id":11364},"le-site","Le site",[13,11367,11368,11369,4566,11372,11374],{},"Ajoute les ",[72,11370,11371],{},"CSS",[72,11373,6121],{}," personnalisés du site. Le theme a été acheté à une époque sur un\nsite proposant des templates, mais à depuis été customizé et adapté pour notre utilisation.",[64,11376,11378],{"id":11377},"ladmin","L'admin",[13,11380,11381],{},"L'interface d'administration permet à femme et à moi d'accéder aux différents produit, d'en créer de\nnouveau, de les modifiers. L'interface se base sur un thème bootstrape et reste assez simple. Seul\nl'utilisateur avec le role d'administrateur peut accéder à cette page.",[64,11383,11385],{"id":11384},"dans-lavenir","Dans l'avenir",[13,11387,11388,11389,11392,11393,11397],{},"Le découpage actuel ne me plaît pas forcément. En effet, je charge des ",[72,11390,11391],{},"bundles"," propres à l'admin\nsur le site alors que ces derniers pourraient être chargés uniquement dans le cadre de l'admin et\ninversement. Je pense qu'appliquer une architecture comme celle décrite ici:\n",[23,11394,11395],{"href":11395,"rel":11396},"http:\u002F\u002Fjolicode.com\u002Fblog\u002Fmultiple-applications-with-symfony2",[27],",\npourrait être une bonne idée pour mieux découper l'application.",[13,11399,11400],{},"Dans le même style, la partie CMS actuelle est liée au site, et j'aimerais la découpler du site pour\npouvoir l'utiliser assez facilement dans d'autres projets.",[133,11402,11404],{"id":11403},"mes-contributions","Mes contributions",[13,11406,11407],{},"Lors du développement de mon projet, j'ai eu besoin de certaines fonctionnalités que je n'ai pas\ntrouvées dans les bundles existants ou qui ne me convenaient pas. Je vous présente ici différents\nprojets que j'ai développé pour pallier à ces manques, sachant que pour l'instant ceux-ci ne sont\npas parfaits et voir même, la documentation peut laisser à désirer (quand aux tests unitaires ils\nsont dans le néant).",[13,11409,11410],{},"Si vous souhaiter aider ou contribuer, n'hésitez pas.",[64,11412,11414],{"id":11413},"collectionbundle","CollectionBundle",[13,11416,11417,11418],{},"Lien: ",[23,11419,11414],{"href":11420,"rel":11421},"http:\u002F\u002Fhg.shadoware.org\u002FSoftware\u002FSymfony2Bundle\u002FCollectionBundle",[27],[13,11423,11424],{},"Dans symfony, il est possible d'ajouter dans un formulaire un type collection pour permettre à un\nutilisateur de saisir une collection de sous-éléments (jointure de type OneToMany):",[145,11426,11428],{"className":10636,"code":11427,"language":10638,"meta":104,"style":104},"$builder->add('emails', 'collection', array(\n    \u002F\u002F chaque item du tableau sera un champ « email »\n    'type'   => 'email',\n    \u002F\u002F ces options sont passées à chaque type « email »\n    'options'  => array(\n        'required'  => false,\n        'attr'      => array('class' => 'email-box')\n    ),\n));\n",[151,11429,11430,11456,11461,11474,11479,11491,11502,11524,11529],{"__ignoreMap":104},[154,11431,11432,11435,11437,11439,11441,11444,11446,11449,11451,11454],{"class":156,"line":157},[154,11433,11434],{"class":193},"$builder",[154,11436,1535],{"class":174},[154,11438,493],{"class":348},[154,11440,352],{"class":174},[154,11442,11443],{"class":201},"'emails'",[154,11445,2877],{"class":174},[154,11447,11448],{"class":201},"'collection'",[154,11450,2877],{"class":174},[154,11452,11453],{"class":197},"array",[154,11455,771],{"class":174},[154,11457,11458],{"class":156,"line":178},[154,11459,11460],{"class":647},"    \u002F\u002F chaque item du tableau sera un champ « email »\n",[154,11462,11463,11466,11469,11472],{"class":156,"line":208},[154,11464,11465],{"class":201},"    'type'",[154,11467,11468],{"class":174},"   => ",[154,11470,11471],{"class":201},"'email'",[154,11473,1484],{"class":174},[154,11475,11476],{"class":156,"line":215},[154,11477,11478],{"class":647},"    \u002F\u002F ces options sont passées à chaque type « email »\n",[154,11480,11481,11484,11487,11489],{"class":156,"line":234},[154,11482,11483],{"class":201},"    'options'",[154,11485,11486],{"class":174},"  => ",[154,11488,11453],{"class":197},[154,11490,771],{"class":174},[154,11492,11493,11496,11498,11500],{"class":156,"line":256},[154,11494,11495],{"class":201},"        'required'",[154,11497,11486],{"class":174},[154,11499,1807],{"class":228},[154,11501,1484],{"class":174},[154,11503,11504,11507,11510,11512,11514,11517,11519,11522],{"class":156,"line":374},[154,11505,11506],{"class":201},"        'attr'",[154,11508,11509],{"class":174},"      => ",[154,11511,11453],{"class":197},[154,11513,352],{"class":174},[154,11515,11516],{"class":201},"'class'",[154,11518,11313],{"class":174},[154,11520,11521],{"class":201},"'email-box'",[154,11523,447],{"class":174},[154,11525,11526],{"class":156,"line":402},[154,11527,11528],{"class":174},"    ),\n",[154,11530,11531],{"class":156,"line":427},[154,11532,8679],{"class":174},[13,11534,11535],{},"Le problème c'est que dans les formulaires symfony2 il n'est pas possible de gérer des formulaires\ndifférents suivant le sous-type de l'objet (gestion de l'héritage dans l'ORM).",[13,11537,11538],{},"CollectionBundle propose deux nouveaux types :",[48,11540,11541,11544],{},[51,11542,11543],{},"Un type permettant de gérer pour chaque classe fille, un formulaire différent.",[51,11545,11546],{},"Un type permettant de gérer des collections de taille fixe : Exemple toujours 5 éléments, quel\nque soit le nombre d'éléments rééls en base.",[13,11548,11549],{},"Ce bundle est actuellement utilisé uniquement dans la partie admin du site.",[64,11551,11553],{"id":11552},"doctrinemigrationodmbundle","DoctrineMigrationODMBundle",[13,11555,11417,11556],{},[23,11557,11553],{"href":11558,"rel":11559},"http:\u002F\u002Fhg.shadoware.org\u002FSoftware\u002FSymfony2Bundle\u002Fdoctrine-migrations-bundle",[27],[13,11561,11562],{},"Pour l'ORM Doctrine, il existe DoctrineMigrationBundle qui permet de faire des migrations de schéma,\nmais il n'existait pas d'équivalent pour l'ODM gérant MongoDB.",[13,11564,11565],{},"Même si MongoDB est schemaless, et que les données peuvent être migrées à l'execution, je ressens le\nbesoin d'avoir la possiblité d'exécuter des scripts lors des changements de version, pour :",[48,11567,11568,11571,11574],{},[51,11569,11570],{},"ajouter de nouvelles données (nécessaires) dans des tables (car on requête sur ces données).",[51,11572,11573],{},"renommage de collection, suite à gros refactoring.",[51,11575,11576],{},"voir autre",[64,11578,11580],{"id":11579},"fpdi-et-fpdf_tpl","FPDI et FPDF_TPL",[13,11582,11583],{},"Pour la génération des PDF, je génère une première version où chaque page est un élément différent,\npuis je me sers de FPDI pour associer les différentes pages sur une même page (avec la mention\nSPECIMEN ou pas).",[13,11585,11586,11587,11590],{},"Afin d'avoir accès à FPDI je me suis créé les dépôts suivants qui fonctionnent avec ceux de\n",[72,11588,11589],{},"tecnick.com\u002Ftcpdf"," pour les utilisateurs de TCPDF.",[13,11592,11593],{},"Liens:",[48,11595,11596,11602],{},[51,11597,11598],{},[23,11599,11600],{"href":11600,"rel":11601},"http:\u002F\u002Fhg.shadoware.org\u002FSoftware\u002FSymfony2Bundle\u002Ffpdi",[27],[51,11603,11604],{},[23,11605,11606],{"href":11606,"rel":11607},"http:\u002F\u002Fhg.shadoware.org\u002FSoftware\u002FSymfony2Bundle\u002Ffpdf_tpl",[27],[64,11609,11611],{"id":11610},"imageresizerbundle","ImageResizerBundle",[13,11613,11614,11615],{},"Lien : ",[23,11616,11611],{"href":11617,"rel":11618},"http:\u002F\u002Fhg.shadoware.org\u002FSoftware\u002FSymfony2Bundle\u002FImageResizerBundle",[27],[13,11620,11621,11622,11626],{},"Dérivé de\n",[23,11623,11624],{"href":11624,"rel":11625},"https:\u002F\u002Fgithub.com\u002Fnresni\u002FImageResizerBundle",[27],", ce\nbundle ajoute",[48,11628,11629,11632],{},[51,11630,11631],{},"des caches supplémentaires",[51,11633,11634],{},"fournit une URL sur la valeur du cache directement (et de générer le cache lors de l'appel de la\ncommande twig). Cette dernière permet de cacher un peu l'URL utilisée pour accéder à l'image\nd'origine. On ne peut accéder alors qu'à l'URL finale. Comme les ids des images sont des ObjectId,\nil n'est pas possible de retrouver par le nom l'URL de l'image d'origine.",[13,11636,11637],{},"Cette extension est utilisée par toute l'application pour l'affichage de toutes les miniatures.",[64,11639,11641],{"id":11640},"piwikbundle","PiwikBundle",[13,11643,11614,11644],{},[23,11645,11641],{"href":11646,"rel":11647},"http:\u002F\u002Fhg.shadoware.org\u002FSoftware\u002FSymfony2Bundle\u002FPiwikBundle",[27],[13,11649,11650],{},"Ce plugin a été créé afin de pouvoir ajouter la gestion de Piwik dans Symfony2. Les commandes PIWIK\npeuvent être passées au travers d'un service ou au travers de commandes TWIG. Ce plugin gère\négalement la notion d'e-commerce de PIWIK.",[13,11652,11653],{},"Une fois le plugin installé (via composer), il faut l'activer à l'aide de la configuration suivante:",[145,11655,11657],{"className":7982,"code":11656,"language":7984,"meta":104,"style":104},"shadoware_piwik:\n  base_url: http:\u002F\u002Fmonpiwik.monsite # URL de base du serveur PIWIK\n  id_site: 1 # N° du site dans PIWIK\n  hidePiwik: false # Indique s'il faut cacher le tracker par un controlleur interne.\n  tokenId: abcedfghijkmn123456789 # Le token id de l'utilisateur (pour le cas où on cache piwik)\n  heartbeat: ~ # Permet de définir quelques attributs activant la fonctionnalité de heatbeat de piwik.\n",[151,11658,11659,11666,11679,11691,11703,11716],{"__ignoreMap":104},[154,11660,11661,11664],{"class":156,"line":157},[154,11662,11663],{"class":193},"shadoware_piwik",[154,11665,8008],{"class":174},[154,11667,11668,11671,11673,11676],{"class":156,"line":178},[154,11669,11670],{"class":193},"  base_url",[154,11672,4314],{"class":174},[154,11674,11675],{"class":201},"http:\u002F\u002Fmonpiwik.monsite",[154,11677,11678],{"class":647}," # URL de base du serveur PIWIK\n",[154,11680,11681,11684,11686,11688],{"class":156,"line":208},[154,11682,11683],{"class":193},"  id_site",[154,11685,4314],{"class":174},[154,11687,106],{"class":228},[154,11689,11690],{"class":647}," # N° du site dans PIWIK\n",[154,11692,11693,11696,11698,11700],{"class":156,"line":215},[154,11694,11695],{"class":193},"  hidePiwik",[154,11697,4314],{"class":174},[154,11699,1807],{"class":228},[154,11701,11702],{"class":647}," # Indique s'il faut cacher le tracker par un controlleur interne.\n",[154,11704,11705,11708,11710,11713],{"class":156,"line":234},[154,11706,11707],{"class":193},"  tokenId",[154,11709,4314],{"class":174},[154,11711,11712],{"class":201},"abcedfghijkmn123456789",[154,11714,11715],{"class":647}," # Le token id de l'utilisateur (pour le cas où on cache piwik)\n",[154,11717,11718,11721,11723,11726],{"class":156,"line":256},[154,11719,11720],{"class":193},"  heartbeat",[154,11722,4314],{"class":174},[154,11724,11725],{"class":228},"~",[154,11727,11728],{"class":647}," # Permet de définir quelques attributs activant la fonctionnalité de heatbeat de piwik.\n",[13,11730,11731],{},"Une fois la configuration faite, le plugin ajoutera juste avant chaque balise  l'appel à\npiwik (en utilisant la méthode asynchrone). Si la page ne contient pas de balise  ou si elle\nconstitue une page de redirection, les informations seront déportées à l'affichage suivant.",[13,11733,11734],{},"Cette dernière fonctionnalité permet par exemple d'ajouter des éléménts au panier e-commerce piwik\nlors des pages de redirection, et de traiter son affichage dès que possible. Cela a par contre comme\nlimitation de ne pas gérer les conflits.",[13,11736,11737],{},"L'utilisation depuis un controlleur se fait grâce à l'utilisation du service:",[145,11739,11741],{"className":10636,"code":11740,"language":10638,"meta":104,"style":104},"$this->container->get('shadoware_piwik.service')->addEcommerceItem($productId, $productName, $category, $amount);\n$this->container->get('shadoware_piwik.service')->trackEcommerceCartUpdate($totalAmount);\n",[151,11742,11743,11788],{"__ignoreMap":104},[154,11744,11745,11747,11749,11752,11754,11756,11758,11761,11763,11766,11768,11771,11773,11776,11778,11781,11783,11786],{"class":156,"line":157},[154,11746,11003],{"class":164},[154,11748,1535],{"class":174},[154,11750,11751],{"class":193},"container",[154,11753,1535],{"class":174},[154,11755,2293],{"class":348},[154,11757,352],{"class":174},[154,11759,11760],{"class":201},"'shadoware_piwik.service'",[154,11762,10938],{"class":174},[154,11764,11765],{"class":348},"addEcommerceItem",[154,11767,352],{"class":174},[154,11769,11770],{"class":193},"$productId",[154,11772,2877],{"class":174},[154,11774,11775],{"class":193},"$productName",[154,11777,2877],{"class":174},[154,11779,11780],{"class":193},"$category",[154,11782,2877],{"class":174},[154,11784,11785],{"class":193},"$amount",[154,11787,362],{"class":174},[154,11789,11790,11792,11794,11796,11798,11800,11802,11804,11806,11809,11811,11814],{"class":156,"line":178},[154,11791,11003],{"class":164},[154,11793,1535],{"class":174},[154,11795,11751],{"class":193},[154,11797,1535],{"class":174},[154,11799,2293],{"class":348},[154,11801,352],{"class":174},[154,11803,11760],{"class":201},[154,11805,10938],{"class":174},[154,11807,11808],{"class":348},"trackEcommerceCartUpdate",[154,11810,352],{"class":174},[154,11812,11813],{"class":193},"$totalAmount",[154,11815,362],{"class":174},[13,11817,11818],{},"L'utilisation depuis une page twig se fait à l'aide des commandes twig. Par exemple dans la page\ntwig de base:",[145,11820,11824],{"className":11821,"code":11822,"language":11823,"meta":104,"style":104},"language-twig shiki shiki-themes one-dark-pro","\u003Ctitle>{{ 'title' | trans }} - {% block title %}{{ 'menu.home' | trans }}{% endblock %}\u003C\u002Ftitle>\n{{ setPiwikPageName(block('title')) }}\n","twig",[151,11825,11826,11877],{"__ignoreMap":104},[154,11827,11828,11830,11833,11836,11838,11841,11844,11847,11850,11853,11856,11859,11861,11863,11866,11869,11872,11874],{"class":156,"line":157},[154,11829,293],{"class":174},[154,11831,11832],{"class":193},"title",[154,11834,11835],{"class":174},">{{ ",[154,11837,11233],{"class":201},[154,11839,11840],{"class":174}," | ",[154,11842,11843],{"class":193},"trans",[154,11845,11846],{"class":174}," }} - {% ",[154,11848,11849],{"class":160},"block",[154,11851,11852],{"class":193}," title",[154,11854,11855],{"class":174}," %}{{ ",[154,11857,11858],{"class":201},"'menu.home'",[154,11860,11840],{"class":174},[154,11862,11843],{"class":193},[154,11864,11865],{"class":174}," }}{% ",[154,11867,11868],{"class":160},"endblock",[154,11870,11871],{"class":174}," %}\u003C\u002F",[154,11873,11832],{"class":193},[154,11875,11876],{"class":174},">\n",[154,11878,11879,11882,11884,11886,11888],{"class":156,"line":178},[154,11880,11881],{"class":174},"{{ setPiwikPageName(",[154,11883,11849],{"class":197},[154,11885,352],{"class":174},[154,11887,11233],{"class":201},[154,11889,11890],{"class":174},")) }}\n",[7043,11892,6958],{"id":6957},[13,11894,11895],{},"Bon voilà j'espère vous avoir fait découvrir le site ainsi que quelques nouveaux plugins\nintéressant.",[13,11897,11898],{},"A bientôt,",[4151,11900,11901],{},"html pre.shiki code .sV9Aq, html code.shiki .sV9Aq{--shiki-default:#7F848E;--shiki-default-font-style:italic}html pre.shiki code .seHd6, html code.shiki .seHd6{--shiki-default:#C678DD}html pre.shiki code .sU0A5, html code.shiki .sU0A5{--shiki-default:#E5C07B}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .shdRp, html code.shiki .shdRp{--shiki-default:#C678DD;--shiki-default-font-style:italic}html pre.shiki code .sKU4T, html code.shiki .sKU4T{--shiki-default:#E5C07B;--shiki-default-font-style:italic}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}",{"title":104,"searchDepth":178,"depth":178,"links":11903},[11904,11905,11906,11907,11908,11909,11910,11911,11912,11913,11914,11917,11918,11919,11920,11921],{"id":10437,"depth":208,"text":10438},{"id":10516,"depth":208,"text":10517},{"id":10539,"depth":208,"text":10512},{"id":10586,"depth":208,"text":10587},{"id":10596,"depth":178,"text":10597},{"id":11333,"depth":178,"text":11334},{"id":11343,"depth":178,"text":11344},{"id":11353,"depth":178,"text":11354},{"id":11364,"depth":178,"text":11365},{"id":11377,"depth":178,"text":11378},{"id":11384,"depth":178,"text":11385,"children":11915},[11916],{"id":11403,"depth":208,"text":11404},{"id":11413,"depth":178,"text":11414},{"id":11552,"depth":178,"text":11553},{"id":11579,"depth":178,"text":11580},{"id":11610,"depth":178,"text":11611},{"id":11640,"depth":178,"text":11641},"Logiciels","logiciels","2014-03-19",{"type":10,"value":11926},[11927,11929,11931,11936,11941],[133,11928,10438],{"id":10437},[13,11930,4216],{},[13,11932,10443,11933,10449],{},[23,11934,10448],{"href":10446,"rel":11935},[27],[13,11937,11938,10457],{},[23,11939,10456],{"href":10454,"rel":11940},[27],[13,11942,10460],{},{},"\u002Fpost\u002Fmonlivretdemesse-fr",{"title":10432,"description":104},"monlivretdemesse-fr","posts\u002FLogiciels\u002F2014-03-19-monlivretdemesse-fr",[11949,11950,11951,11952],"dedie","livrets","mariage","bapteme","CLzxUTrMi7D7_9aacVnIiqCI2iUc0MClvm1Kv0yZXCA",{"id":11955,"title":11956,"author":8,"body":11957,"category":11922,"categorySlug":11923,"date":12939,"description":7030,"excerpt":12940,"extension":4199,"location":4200,"meta":12950,"navigation":211,"path":12951,"published":211,"seo":12952,"slug":12953,"stem":12954,"tags":12955,"timeToRead":402,"__hash__":12956},"posts\u002Fposts\u002FLogiciels\u002F2013-01-12-migration-to-pelican.md","Passage du site sous Pelican",{"type":10,"value":11958,"toc":12925},[11959,11961,11964,11967,11970,11974,11977,11980,11983,11986,11989,11992,12003,12006,12022,12025,12029,12032,12035,12038,12042,12045,12048,12052,12063,12069,12072,12075,12079,12086,12093,12097,12106,12109,12140,12143,12146,12163,12166,12169,12173,12176,12186,12189,12298,12301,12361,12364,12468,12485,12489,12496,12499,12517,12523,12529,12533,12536,12543,12711,12714,12717,12723,12727,12730,12733,12736,12739,12784,12794,12798,12801,12824,12834,12904,12907,12922],[13,11960,7030],{},[13,11962,11963],{},"Cela fait bien longtemps que je n'ai rien écrit sur ce site. Et pour\ncause, je suis bien occupé 😄.",[13,11965,11966],{},"J'ai quand même pris un peu de temps récemment pour passer entièrement mon\nsite sous Pelican.",[13,11968,11969],{},"C'est mon cadeau de Noël de Geek ;).",[64,11971,11973],{"id":11972},"quest-ce-que-pelican","Qu'est-ce que Pelican ?",[13,11975,11976],{},"Pelican est un gestionnaire de blog statique.",[13,11978,11979],{},"Qu'est-ce que ça veut dire ?",[13,11981,11982],{},"Cela signifie que je vais écrire mes billets avec mon éditeur de texte\npréféré (kwrite), au format Markdown (ou restructuredText), et que je\ngénère mon blog au format HTML avant publication. (De la même manière que\nl'on compilerait un programme).",[13,11984,11985],{},"Le serveur n'a alors besoin de servir que des fichiers statiques, il n'y\na donc pas de surplus de mémoire, ou de délai dû à la nécessité de\ngénérer les pages.",[13,11987,11988],{},"Les pages n'étant pas modifiées tous les jours, il n'y a de toute façon\npas d'intérêt de générer les pages à chaque accès.",[13,11990,11991],{},"Les avantages indéniables sont les suivants :",[48,11993,11994,11997,12000],{},[51,11995,11996],{},"Pas de base de données, pas de page générée à la volée : donc pas de\ntemps de latence. La page est servie dés qu'elle est demandée. De plus\nil existe un plugin permettant de générer les pages au format pré-zippé,\nce qui fait un traitement de moins sur le serveur.",[51,11998,11999],{},"Pas de faille de sécurité dû à un script mal paramétré.",[51,12001,12002],{},"Je peux gérer mes billets sous un gestionnaire de version (exemple\nMercurial).",[13,12004,12005],{},"Par contre les inconvénients sont :",[48,12007,12008,12016,12019],{},[51,12009,12010,12011,1076],{},"Plus de commentaire direct sur le blog (sauf à utiliser des plateformes\ntel que ",[23,12012,12015],{"href":12013,"rel":12014},"http:\u002F\u002Fdisqus.com\u002F",[27],"disqus",[51,12017,12018],{},"Je dois installer le générateur de blog sur la\u002Fles machines que je\nsouhaite utiliser pour générer le blog.",[51,12020,12021],{},"Je dois ensuite utiliser SCP pour transférer mon blog.",[13,12023,12024],{},"Pour les deux derniers points, ils sont de mon choix. D'autres installent\nces outils sur le serveur, ils peuvent donc générer leur page directement\nà partir du serveur.",[64,12026,12028],{"id":12027},"les-implications","Les implications",[13,12030,12031],{},"Les conséquences pour ce blog sont donc : plus de commentaire (jusqu'à\nnouvel ordre). Vous pouvez toujours m'envoyer un mail, pour poser une\nquestion, je l'ajouterai manuellement en tant que commentaire.",[13,12033,12034],{},"Je garde tout de même les anciens commentaires, qui peuvent contenir\nparfois quelques informations utiles.",[13,12036,12037],{},"J'ai également profité de ce petit changement pour faire un petit\nnettoyage et j'ai supprimé tous les billets d'humeur, pour ne garder que\nles articles réellement intéressant.",[64,12039,12041],{"id":12040},"comment-sest-déroulé-la-conversion","Comment s'est déroulé la conversion ?",[13,12043,12044],{},"Dans cette partie, je vais décrire le déroulement de la conversion du\nblog.",[13,12046,12047],{},"Cette conversion comprend les articles, les commentaires et le thème.",[133,12049,12051],{"id":12050},"exportation-des-billets-provenant-de-dotclear","Exportation des billets provenant de dotclear",[13,12053,12054,12055,12058,12059,12062],{},"Il a d'abord fallu exporter les billets depuis le site sous\n",[72,12056,12057],{},"Dotclear",". Sous Dotclear, dans la partie administration, il est possible\nd'utiliser le plugin ",[151,12060,12061],{},"Import\u002FExport"," pour exporter le site dans un fichier\ntexte.",[13,12064,12065],{},[123,12066],{"alt":12067,"src":12068},"Export","\u002FLogiciels\u002Fmigration\u002Fdotclear_export.png",[13,12070,12071],{},"Le fichier texte se présente alors comme un mixte entre un fichier INI et\nun ensemble de fichier CSV. Il contient, les catégories, les articles, et\nles commentaires.",[13,12073,12074],{},"Ce fichier peut alors être utilisé avec l'outil d'import de pelican pour\nrécupérer les différents billets.",[133,12076,12078],{"id":12077},"création-dun-environnement-pour-pelican","Création d'un environnement pour pelican",[13,12080,12081,12082,12085],{},"La création de l'environnement se fait comme décrit sur le ",[154,12083,12084],{},"site de\npelican",". Je ne vais donc pas le décrire une fois de plus.",[13,12087,12088,12089,12092],{},"Une fois installé, les billets sont à placer dans le dossier ",[151,12090,12091],{},"content",".\nLes fichiers générés par l'import seront donc placés dans ce dossier.",[133,12094,12096],{"id":12095},"importation-des-billets-au-format-markdown","Importation des billets au format Markdown",[13,12098,12099,12100,12105],{},"L'import est décrit dans la ",[23,12101,12104],{"href":12102,"rel":12103},"http:\u002F\u002Fdocs.getpelican.com\u002Fen\u002Flatest\u002Fimporter.html",[27],"documentation de pelican",". Après différents\ntests, l'import au format Markdown a été celui qui a donné de meilleurs\nrésultats.",[13,12107,12108],{},"Voici donc la commande qui m'a permis de réaliser cet import :",[145,12110,12112],{"className":8456,"code":12111,"language":8458,"meta":104,"style":104},"pelican-import --dotclear -m markdown --dir-cat 2012-12-11-shadoware-backup.txt -o content\n",[151,12113,12114],{"__ignoreMap":104},[154,12115,12116,12119,12122,12125,12128,12131,12134,12137],{"class":156,"line":157},[154,12117,12118],{"class":348},"pelican-import",[154,12120,12121],{"class":228}," --dotclear",[154,12123,12124],{"class":228}," -m",[154,12126,12127],{"class":201}," markdown",[154,12129,12130],{"class":228}," --dir-cat",[154,12132,12133],{"class":201}," 2012-12-11-shadoware-backup.txt",[154,12135,12136],{"class":228}," -o",[154,12138,12139],{"class":201}," content\n",[13,12141,12142],{},"Cela m'a généré une liste de 65 fichiers : 1 par billet. Parmi ces\nbillets, j'ai fait le tri, et j'ai supprimé les billets d'humeur. J'ai\najouté également un tag indiquant l'état de draft sur quelques billets qui\nsont en cours d'écriture et que je n'ai pas encore eu le temps de\nterminer et de publier.",[13,12144,12145],{},"Puis j'ai effectué une passe sur chaque billet, pour",[48,12147,12148,12151,12154,12157,12160],{},[51,12149,12150],{},"reprendre les parties de code (coloration syntaxique),",[51,12152,12153],{},"reprendre les tableaux,",[51,12155,12156],{},"reprendre les tags (qui n'ont pas été importés),",[51,12158,12159],{},"corriger les dates (la date était celle de création du poste, et non\ncelle de publication),",[51,12161,12162],{},"modification des catégories.",[13,12164,12165],{},"Sous dotclear, j'avais fait plusieurs niveaux de catégories, alors que\nsous pelican, il n'y a qu'un seul niveau de catégorie. J'ai donc dû mettre\nà plat les différentes catégories.",[13,12167,12168],{},"Pour les tableaux, ces derniers étaient repris directement au format HTML,\nils étaient donc exploitables directement. J'ai préféré refaire les\ntableaux au format Markdown.",[133,12170,12172],{"id":12171},"ajustement-du-fichier-de-configuration","Ajustement du fichier de configuration",[13,12174,12175],{},"Viennent ensuite les ajustements faits dans le fichier de configuration,\npour que les URL des articles sous dotclear soient les plus proches\npossibles des nouvelles URL.",[13,12177,12178,12179,12182,12183,298],{},"Pour cela, j'ai choisi d'avoir les mêmes adresses que sur le site dotclear\nsuivies de ",[151,12180,12181],{},".html"," ou de ",[151,12184,12185],{},".xml",[13,12187,12188],{},"Dans le fichier, j'ai positionné les variables pelicanconf.py",[145,12190,12194],{"className":12191,"code":12192,"language":12193,"meta":104,"style":104},"language-python shiki shiki-themes one-dark-pro","ARTICLE_URL = 'post\u002F{slug}.html'\nARTICLE_SAVE_AS = 'post\u002F{slug}.html'\nAUTHOR_SAVE_AS = False\n\nFEED_DOMAIN = SITEURL\nFEED_ALL_ATOM = 'feed\u002Fatom.xml'\nCATEGORY_FEED_ATOM = 'feed\u002Fcategory\u002F%s\u002Fatom.xml'\nTAG_FEED_ATOM = 'feed\u002Ftags\u002F%s\u002Fatom.xml'\nTRANSLATION_FEED_ATOM = False\n","python",[151,12195,12196,12212,12225,12235,12239,12249,12259,12275,12289],{"__ignoreMap":104},[154,12197,12198,12201,12203,12206,12209],{"class":156,"line":157},[154,12199,12200],{"class":228},"ARTICLE_URL",[154,12202,2968],{"class":197},[154,12204,12205],{"class":201}," 'post\u002F",[154,12207,12208],{"class":228},"{slug}",[154,12210,12211],{"class":201},".html'\n",[154,12213,12214,12217,12219,12221,12223],{"class":156,"line":178},[154,12215,12216],{"class":228},"ARTICLE_SAVE_AS",[154,12218,2968],{"class":197},[154,12220,12205],{"class":201},[154,12222,12208],{"class":228},[154,12224,12211],{"class":201},[154,12226,12227,12230,12232],{"class":156,"line":208},[154,12228,12229],{"class":228},"AUTHOR_SAVE_AS",[154,12231,2968],{"class":197},[154,12233,12234],{"class":228}," False\n",[154,12236,12237],{"class":156,"line":215},[154,12238,212],{"emptyLinePlaceholder":211},[154,12240,12241,12244,12246],{"class":156,"line":234},[154,12242,12243],{"class":228},"FEED_DOMAIN",[154,12245,2968],{"class":197},[154,12247,12248],{"class":228}," SITEURL\n",[154,12250,12251,12254,12256],{"class":156,"line":256},[154,12252,12253],{"class":228},"FEED_ALL_ATOM",[154,12255,2968],{"class":197},[154,12257,12258],{"class":201}," 'feed\u002Fatom.xml'\n",[154,12260,12261,12264,12266,12269,12272],{"class":156,"line":374},[154,12262,12263],{"class":228},"CATEGORY_FEED_ATOM",[154,12265,2968],{"class":197},[154,12267,12268],{"class":201}," 'feed\u002Fcategory\u002F",[154,12270,12271],{"class":228},"%s",[154,12273,12274],{"class":201},"\u002Fatom.xml'\n",[154,12276,12277,12280,12282,12285,12287],{"class":156,"line":402},[154,12278,12279],{"class":228},"TAG_FEED_ATOM",[154,12281,2968],{"class":197},[154,12283,12284],{"class":201}," 'feed\u002Ftags\u002F",[154,12286,12271],{"class":228},[154,12288,12274],{"class":201},[154,12290,12291,12294,12296],{"class":156,"line":427},[154,12292,12293],{"class":228},"TRANSLATION_FEED_ATOM",[154,12295,2968],{"class":197},[154,12297,12234],{"class":228},[13,12299,12300],{},"Dans le fichier, j'ai positionné les variables publishconf.py",[145,12302,12304],{"className":12191,"code":12303,"language":12193,"meta":104,"style":104},"ARTICLE_URL = 'post\u002F{slug}'\nPAGE_URL = 'pages\u002F{slug}'\nCATEGORY_URL = 'category\u002F{slug}'\nTAG_URL = 'tag\u002F{slug}'\n",[151,12305,12306,12319,12333,12347],{"__ignoreMap":104},[154,12307,12308,12310,12312,12314,12316],{"class":156,"line":157},[154,12309,12200],{"class":228},[154,12311,2968],{"class":197},[154,12313,12205],{"class":201},[154,12315,12208],{"class":228},[154,12317,12318],{"class":201},"'\n",[154,12320,12321,12324,12326,12329,12331],{"class":156,"line":178},[154,12322,12323],{"class":228},"PAGE_URL",[154,12325,2968],{"class":197},[154,12327,12328],{"class":201}," 'pages\u002F",[154,12330,12208],{"class":228},[154,12332,12318],{"class":201},[154,12334,12335,12338,12340,12343,12345],{"class":156,"line":208},[154,12336,12337],{"class":228},"CATEGORY_URL",[154,12339,2968],{"class":197},[154,12341,12342],{"class":201}," 'category\u002F",[154,12344,12208],{"class":228},[154,12346,12318],{"class":201},[154,12348,12349,12352,12354,12357,12359],{"class":156,"line":215},[154,12350,12351],{"class":228},"TAG_URL",[154,12353,2968],{"class":197},[154,12355,12356],{"class":201}," 'tag\u002F",[154,12358,12208],{"class":228},[154,12360,12318],{"class":201},[13,12362,12363],{},"Du coté de nginx, le serveur d'application, j'ai ajouté les lignes\nsuivantes :",[145,12365,12369],{"className":12366,"code":12367,"language":12368,"meta":104,"style":104},"language-nginx shiki shiki-themes one-dark-pro","rewrite ^\u002Fdotclear\u002Findex.php\u002Fpost\u002F\\d+\u002F\\d+\u002F\\d+\u002F\\d+-(.*)$ \u002Fpost\u002F$1 permanent;\nrewrite ^\u002Fpost\u002F\\d+\u002F\\d+\u002F\\d+\u002F\\d+-(.*)$ \u002Fpost\u002F$1 permanent;\nrewrite ^\u002Fpost\u002F\\d+\u002F\\d+\u002F\\d+\u002F(.*)$ \u002Fpost\u002F$1 permanent;\n\nlocation \u002F {\n    try_files $uri.html $uri.xml $uri $uri\u002F =404;\n}\n","nginx",[151,12370,12371,12389,12404,12419,12423,12430,12464],{"__ignoreMap":104},[154,12372,12373,12376,12379,12382,12384,12387],{"class":156,"line":157},[154,12374,12375],{"class":160},"rewrite",[154,12377,12378],{"class":193}," ^\u002Fdotclear\u002Findex.php\u002Fpost\u002F\\d+\u002F\\d+\u002F\\d+\u002F\\d+-(.*)$",[154,12380,12381],{"class":174}," \u002Fpost\u002F$",[154,12383,106],{"class":193},[154,12385,12386],{"class":160}," permanent",[154,12388,205],{"class":174},[154,12390,12391,12393,12396,12398,12400,12402],{"class":156,"line":178},[154,12392,12375],{"class":160},[154,12394,12395],{"class":193}," ^\u002Fpost\u002F\\d+\u002F\\d+\u002F\\d+\u002F\\d+-(.*)$",[154,12397,12381],{"class":174},[154,12399,106],{"class":193},[154,12401,12386],{"class":160},[154,12403,205],{"class":174},[154,12405,12406,12408,12411,12413,12415,12417],{"class":156,"line":208},[154,12407,12375],{"class":160},[154,12409,12410],{"class":193}," ^\u002Fpost\u002F\\d+\u002F\\d+\u002F\\d+\u002F(.*)$",[154,12412,12381],{"class":174},[154,12414,106],{"class":193},[154,12416,12386],{"class":160},[154,12418,205],{"class":174},[154,12420,12421],{"class":156,"line":215},[154,12422,212],{"emptyLinePlaceholder":211},[154,12424,12425,12427],{"class":156,"line":234},[154,12426,1690],{"class":160},[154,12428,12429],{"class":174}," \u002F {\n",[154,12431,12432,12435,12438,12441,12444,12446,12449,12451,12454,12456,12459,12462],{"class":156,"line":256},[154,12433,12434],{"class":160},"    try_files ",[154,12436,12437],{"class":174},"$",[154,12439,12440],{"class":193},"uri",[154,12442,12443],{"class":174},".html $",[154,12445,12440],{"class":193},[154,12447,12448],{"class":174},".xml $",[154,12450,12440],{"class":193},[154,12452,12453],{"class":174}," $",[154,12455,12440],{"class":193},[154,12457,12458],{"class":174},"\u002F ",[154,12460,12461],{"class":228},"=404",[154,12463,205],{"class":174},[154,12465,12466],{"class":156,"line":374},[154,12467,4329],{"class":174},[13,12469,12470,12471,12474,12475,12478,12479,12481,12482,12484],{},"Les premières lignes permettent de reprendre les anciennes adresses\n",[151,12472,12473],{},"dotclear",", et la commande ",[151,12476,12477],{},"try_files"," permet de reprendre les adresses\nexistantes et de les compléter par ",[151,12480,12181],{}," et par ",[151,12483,12185],{}," selon le besoin.",[133,12486,12488],{"id":12487},"modification-du-thème","Modification du thème",[13,12490,12491,12492,12495],{},"Enfin j'ai écrit un nouveau thème dans le dossier ",[151,12493,12494],{},"themes\u002Fshadoware",". Ce\nthème est fait pour coller le plus possible à celui que j'avais sur le\nsite dotclear.",[13,12497,12498],{},"Il génère donc la page de la même manière que dotclear le faisait (à\nquelques différences prés) pour éviter de devoir ré-écrire la feuille de\nstyle CSS.",[13,12500,12501,12502,12507,12508,4783,12511,12514,12515,298],{},"L'écriture du thème n'est en soit pas très compliqué, car ",[23,12503,12506],{"href":12504,"rel":12505},"http:\u002F\u002Fjinja.pocoo.org\u002Fdocs\u002F",[27],"jinja2"," est\nassez simple d'utilisation. Je me suis inspiré des thèmes ",[151,12509,12510],{},"simple",[151,12512,12513],{},"notmyidea"," pour écrire le thème, basé sur celui que j'avais sous\n",[151,12516,12473],{},[13,12518,12519,12520,298],{},"Vous pouvez trouver le thème au ",[154,12521,12522],{},"lien suivant",[13,12524,12525],{},[123,12526],{"alt":12527,"src":12528},"Theme","\u002FLogiciels\u002Fmigration\u002Fsite.png",[133,12530,12532],{"id":12531},"importation-des-commentaires","Importation des commentaires",[13,12534,12535],{},"Les commentaires existants sur mon blog actuel peuvent parfois contenir\ndes informations complémentaires. Les ignorer lors de la migration est\ntout a fait possible, mais j'ai préféré les récupérer.",[13,12537,12538,12539,12542],{},"Pour cela j'ai installé le ",[154,12540,12541],{},"plugin comments",". Ensuite j'ai modifié le\nthème pour ajouter l'affichage des commentaires :",[145,12544,12548],{"className":12545,"code":12546,"language":12547,"meta":104,"style":104},"language-html shiki shiki-themes one-dark-pro","{% if article.comments %}\n\u003Cdiv id=\"comments\">\n  \u003Ch3>Commentaires\u003C\u002Fh3>\n  \u003Cdl>\n    {% for comment in article.comments %}\n    \u003Cdt>\n      \u003Ca class=\"comment-number\" href=\"#\">{{ loop.index }}.\u003C\u002Fa>\n      Le {{ comment.date }} par {{ comment.author }}\n    \u003C\u002Fdt>\n    \u003Cdd>\n      \u003Cp>{{ comment.content }}\u003C\u002Fp>\n    \u003C\u002Fdd>\n    {% endfor %}\n  \u003C\u002Fdl>\n\u003C\u002Fdiv>\n{% endif %}\n","html",[151,12549,12550,12555,12572,12586,12595,12600,12610,12639,12644,12653,12662,12675,12683,12688,12697,12706],{"__ignoreMap":104},[154,12551,12552],{"class":156,"line":157},[154,12553,12554],{"class":174},"{% if article.comments %}\n",[154,12556,12557,12559,12562,12565,12567,12570],{"class":156,"line":178},[154,12558,293],{"class":174},[154,12560,12561],{"class":193},"div",[154,12563,12564],{"class":228}," id",[154,12566,198],{"class":174},[154,12568,12569],{"class":201},"\"comments\"",[154,12571,11876],{"class":174},[154,12573,12574,12577,12579,12582,12584],{"class":156,"line":208},[154,12575,12576],{"class":174},"  \u003C",[154,12578,133],{"class":193},[154,12580,12581],{"class":174},">Commentaires\u003C\u002F",[154,12583,133],{"class":193},[154,12585,11876],{"class":174},[154,12587,12588,12590,12593],{"class":156,"line":215},[154,12589,12576],{"class":174},[154,12591,12592],{"class":193},"dl",[154,12594,11876],{"class":174},[154,12596,12597],{"class":156,"line":234},[154,12598,12599],{"class":174},"    {% for comment in article.comments %}\n",[154,12601,12602,12605,12608],{"class":156,"line":256},[154,12603,12604],{"class":174},"    \u003C",[154,12606,12607],{"class":193},"dt",[154,12609,11876],{"class":174},[154,12611,12612,12615,12617,12619,12621,12624,12627,12629,12632,12635,12637],{"class":156,"line":374},[154,12613,12614],{"class":174},"      \u003C",[154,12616,23],{"class":193},[154,12618,1045],{"class":228},[154,12620,198],{"class":174},[154,12622,12623],{"class":201},"\"comment-number\"",[154,12625,12626],{"class":228}," href",[154,12628,198],{"class":174},[154,12630,12631],{"class":201},"\"#\"",[154,12633,12634],{"class":174},">{{ loop.index }}.\u003C\u002F",[154,12636,23],{"class":193},[154,12638,11876],{"class":174},[154,12640,12641],{"class":156,"line":402},[154,12642,12643],{"class":174},"      Le {{ comment.date }} par {{ comment.author }}\n",[154,12645,12646,12649,12651],{"class":156,"line":427},[154,12647,12648],{"class":174},"    \u003C\u002F",[154,12650,12607],{"class":193},[154,12652,11876],{"class":174},[154,12654,12655,12657,12660],{"class":156,"line":450},[154,12656,12604],{"class":174},[154,12658,12659],{"class":193},"dd",[154,12661,11876],{"class":174},[154,12663,12664,12666,12668,12671,12673],{"class":156,"line":458},[154,12665,12614],{"class":174},[154,12667,13],{"class":193},[154,12669,12670],{"class":174},">{{ comment.content }}\u003C\u002F",[154,12672,13],{"class":193},[154,12674,11876],{"class":174},[154,12676,12677,12679,12681],{"class":156,"line":463},[154,12678,12648],{"class":174},[154,12680,12659],{"class":193},[154,12682,11876],{"class":174},[154,12684,12685],{"class":156,"line":485},[154,12686,12687],{"class":174},"    {% endfor %}\n",[154,12689,12690,12693,12695],{"class":156,"line":499},[154,12691,12692],{"class":174},"  \u003C\u002F",[154,12694,12592],{"class":193},[154,12696,11876],{"class":174},[154,12698,12699,12702,12704],{"class":156,"line":504},[154,12700,12701],{"class":174},"\u003C\u002F",[154,12703,12561],{"class":193},[154,12705,11876],{"class":174},[154,12707,12708],{"class":156,"line":509},[154,12709,12710],{"class":174},"{% endif %}\n",[13,12712,12713],{},"Cela permet d'afficher les commentaires à l'aide du plugin. Par contre le\nproblème est que les commentaires provenant de dotclear ne sont pas\nimportés.",[13,12715,12716],{},"Du coup j'ai modifié le script d'import, pour en faire un qui importe les\ncommentaires. Il doit être lancé séparément du script d'import\ntraditionnel.",[13,12718,12719,12722],{},[154,12720,12721],{},"Voici donc le script"," permettant de récupérer les commentaires.",[133,12724,12726],{"id":12725},"gestion-du-tag-planet-libre","Gestion du tag planet libre",[13,12728,12729],{},"Afin de pouvoir publier des articles sur le planet libre, je proposais\njusqu'ici des articles sur le planet libre à l'aide d'un tag de type\nplanet.",[13,12731,12732],{},"L'utilisation d'un tag de type planet permet de filtrer les articles\nque je souhaite mettre sur le planet libre. Par contre avec pelican,\nl'utilisation de ce tag fausse le calcul des billets liés.",[13,12734,12735],{},"Du coup j'ai décidé que la génération du flux ATOM donné au planet sera\ngénérée avec les articles qui posséderont un metatag déposé au début de\nchaque billet. Sous la présence du tag avec la valeur true, le billet est\najouté dans le flux ATOM des planets.",[13,12737,12738],{},"Pour ajouter le tag, il faut donc au début de l'article écrire le texte\nsuivant :",[145,12740,12744],{"className":12741,"code":12742,"language":12743,"meta":104,"style":104},"language-markdown shiki shiki-themes one-dark-pro","Title: Passage du site sous Pelican\nTags: dedie\nPlanet: true\n\nBonjour à tous,\n\nCela fait bien longtemps que je n'ai rien écrit sur ce site. Et pour\ncause, je suis bien occupé 😄.\n","markdown",[151,12745,12746,12751,12756,12761,12765,12770,12774,12779],{"__ignoreMap":104},[154,12747,12748],{"class":156,"line":157},[154,12749,12750],{"class":174},"Title: Passage du site sous Pelican\n",[154,12752,12753],{"class":156,"line":178},[154,12754,12755],{"class":174},"Tags: dedie\n",[154,12757,12758],{"class":156,"line":208},[154,12759,12760],{"class":174},"Planet: true\n",[154,12762,12763],{"class":156,"line":215},[154,12764,212],{"emptyLinePlaceholder":211},[154,12766,12767],{"class":156,"line":234},[154,12768,12769],{"class":174},"Bonjour à tous,\n",[154,12771,12772],{"class":156,"line":256},[154,12773,212],{"emptyLinePlaceholder":211},[154,12775,12776],{"class":156,"line":374},[154,12777,12778],{"class":174},"Cela fait bien longtemps que je n'ai rien écrit sur ce site. Et pour\n",[154,12780,12781],{"class":156,"line":402},[154,12782,12783],{"class":174},"cause, je suis bien occupé 😄.\n",[13,12785,12786,12787,12790,12791,298],{},"Pour ce faire j'ai écrit une extension à pelican qui génére un flux nommé\n",[151,12788,12789],{},"{OUTPUT_PATH}\u002F{TAG_FEED_ATOM}"," (avec %s remplacé par planet). Vous pouvez\ntélécharger l'",[154,12792,12793],{},"extension au lien suivant",[133,12795,12797],{"id":12796},"création-dune-page-404","Création d'une page 404",[13,12799,12800],{},"Afin d'avoir une belle page 404 si la page n'est pas correctement trouvée\n(par exemple si certaines URLs ne correspondent plus à celle de dotclear),\nj'ai défini au niveau de pelican, la création d'une belle page 404 de la\nmanière suivante :",[145,12802,12804],{"className":12191,"code":12803,"language":12193,"meta":104,"style":104},"TEMPLATE_PAGES = {'error404.html': 'error404.html'}\n",[151,12805,12806],{"__ignoreMap":104},[154,12807,12808,12811,12813,12815,12818,12820,12822],{"class":156,"line":157},[154,12809,12810],{"class":228},"TEMPLATE_PAGES",[154,12812,2968],{"class":197},[154,12814,9215],{"class":174},[154,12816,12817],{"class":201},"'error404.html'",[154,12819,4314],{"class":174},[154,12821,12817],{"class":201},[154,12823,4329],{"class":174},[13,12825,12826,12827,12829,12830,12833],{},"J'ai alors créé dans le dossier ",[151,12828,12091],{}," le fichier ",[151,12831,12832],{},"error404.html"," avec\nle contenu suivant :",[145,12835,12838],{"className":12836,"code":12837,"language":12506,"meta":104,"style":104},"language-jinja2 shiki shiki-themes one-dark-pro","{% extends \"base.html\" %}\n{% block title %}Page non trouvée{% endblock %}\n{% block piwik_title %}404{% endblock %}\n{% block content %}\n\u003Csection id=\"content\">\n    \u003Cdiv id=\"content-info\">\n        \u003Ch2>Document non trouvé\u003C\u002Fh2>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"content-inner\">\n        \u003Cp>Le document que vous cherchez n'existe pas.\u003C\u002Fp>\n    \u003C\u002Fdiv>\n\u003C\u002Fsection>\n{% endblock %}\n",[151,12839,12840,12845,12850,12855,12860,12865,12870,12875,12880,12885,12890,12894,12899],{"__ignoreMap":104},[154,12841,12842],{"class":156,"line":157},[154,12843,12844],{},"{% extends \"base.html\" %}\n",[154,12846,12847],{"class":156,"line":178},[154,12848,12849],{},"{% block title %}Page non trouvée{% endblock %}\n",[154,12851,12852],{"class":156,"line":208},[154,12853,12854],{},"{% block piwik_title %}404{% endblock %}\n",[154,12856,12857],{"class":156,"line":215},[154,12858,12859],{},"{% block content %}\n",[154,12861,12862],{"class":156,"line":234},[154,12863,12864],{},"\u003Csection id=\"content\">\n",[154,12866,12867],{"class":156,"line":256},[154,12868,12869],{},"    \u003Cdiv id=\"content-info\">\n",[154,12871,12872],{"class":156,"line":374},[154,12873,12874],{},"        \u003Ch2>Document non trouvé\u003C\u002Fh2>\n",[154,12876,12877],{"class":156,"line":402},[154,12878,12879],{},"    \u003C\u002Fdiv>\n",[154,12881,12882],{"class":156,"line":427},[154,12883,12884],{},"    \u003Cdiv class=\"content-inner\">\n",[154,12886,12887],{"class":156,"line":450},[154,12888,12889],{},"        \u003Cp>Le document que vous cherchez n'existe pas.\u003C\u002Fp>\n",[154,12891,12892],{"class":156,"line":458},[154,12893,12879],{},[154,12895,12896],{"class":156,"line":463},[154,12897,12898],{},"\u003C\u002Fsection>\n",[154,12900,12901],{"class":156,"line":485},[154,12902,12903],{},"{% endblock %}\n",[13,12905,12906],{},"Enfin, comme j'utilise le serveur nginx pour servir mes pages j'ai ajouté\nla règle (toute simple) suivante pour définir ma page 404 :",[145,12908,12910],{"className":12366,"code":12909,"language":12368,"meta":104,"style":104},"error_page 404 \u002Ferror404.html;\n",[151,12911,12912],{"__ignoreMap":104},[154,12913,12914,12917,12919],{"class":156,"line":157},[154,12915,12916],{"class":160},"error_page ",[154,12918,6393],{"class":228},[154,12920,12921],{"class":174}," \u002Ferror404.html;\n",[4151,12923,12924],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html pre.shiki code .seHd6, html code.shiki .seHd6{--shiki-default:#C678DD}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}",{"title":104,"searchDepth":178,"depth":178,"links":12926},[12927,12928,12929],{"id":11972,"depth":178,"text":11973},{"id":12027,"depth":178,"text":12028},{"id":12040,"depth":178,"text":12041,"children":12930},[12931,12932,12933,12934,12935,12936,12937,12938],{"id":12050,"depth":208,"text":12051},{"id":12077,"depth":208,"text":12078},{"id":12095,"depth":208,"text":12096},{"id":12171,"depth":208,"text":12172},{"id":12487,"depth":208,"text":12488},{"id":12531,"depth":208,"text":12532},{"id":12725,"depth":208,"text":12726},{"id":12796,"depth":208,"text":12797},"2013-01-12",{"type":10,"value":12941},[12942,12944,12946,12948],[13,12943,7030],{},[13,12945,11963],{},[13,12947,11966],{},[13,12949,11969],{},{"planet":211},"\u002Fpost\u002Fmigration-to-pelican",{"title":11956,"description":7030},"migration-to-pelican","posts\u002FLogiciels\u002F2013-01-12-migration-to-pelican",[11949],"RjPEE1XKQzoFIQDsH16W_XStvfsUCDU0KyWZDWKPIXs",{"id":12958,"title":12959,"author":8,"body":12960,"category":4164,"categorySlug":4165,"date":14762,"description":104,"excerpt":14763,"extension":4199,"location":4200,"meta":14804,"navigation":211,"path":14805,"published":211,"seo":14806,"slug":14807,"stem":14808,"tags":14809,"timeToRead":427,"__hash__":14810},"posts\u002Fposts\u002FProgrammation\u002F2012-07-01-cross-compilation-compiler-un-programme-pour-ms-windows-sous-gnu-linux.md","Cross-Compilation - Compiler un programme pour MS\u002FWindows sous Gnu\u002FLinux",{"type":10,"value":12961,"toc":14752},[12962,12972,12975,12978,12981,13007,13010,13016,13020,13036,13039,13064,13067,13078,13081,13100,13109,13112,13222,13225,13228,13302,13305,13362,13365,13380,13383,13415,13418,13769,13772,14068,14071,14075,14078,14081,14197,14204,14258,14265,14271,14274,14282,14288,14291,14297,14300,14311,14314,14346,14358,14634,14637,14656,14662,14664,14667,14749],[133,12963,12965,12966,12971],{"id":12964},"quest-que-la-cross-compilation1","Qu'est que la cross-compilation",[97,12967,12968],{},[23,12969,106],{"href":101,"ariaDescribedBy":12970,"dataFootnoteRef":104,"id":105},[103]," ?",[13,12973,12974],{},"La cross compilation est la possibilité sur une machine avec un matériel\nspécifique (architecture) et avec un système d'exploitation donné, de\ncompiler des programmes pour une autre architecture, ou pour un autre\nsystème d'exploitation.",[13,12976,12977],{},"Cela peut être utilisé par exemple pour compiler un programme sur votre\nordinateur de tous les jours (sous Gnu\u002FLinux, avec une architecture\ni386) à destination de votre téléphone mobile, qui lui est sous Symbian\navec un processeur ARM.",[13,12979,12980],{},"Les raisons de faire de la compilation croisée peuvent donc être\nmultiples :",[48,12982,12983,12986,12994,13004],{},[51,12984,12985],{},"Éviter de redémarrer votre machine pour compiler vos binaires.",[51,12987,12988,12989,2300],{},"Disponibilité des outils sur votre machine \u002F Indisponibilité des\noutils de compilation sur la machine de destination (on trouve\nrarement des outils de compilation sur des téléphones\nportables",[97,12990,12991],{},[23,12992,1447],{"href":1444,"ariaDescribedBy":12993,"dataFootnoteRef":104,"id":1446},[103],[51,12995,12996,12997,2300],{},"Puissances des calculs (la compilation prendra moins de temps sur\nvotre PC de bureau que sur votre appareil mobile",[97,12998,12999],{},[23,13000,4701],{"href":13001,"ariaDescribedBy":13002,"dataFootnoteRef":104,"id":13003},"#user-content-fn-3",[103],"user-content-fnref-3",[51,13005,13006],{},"Licence : Vous voulez compiler à destination d'un système\nd'exploitation que vous ne possédez pas",[13,13008,13009],{},"Attention: La compilation croisée ne garantie pas que programme\nfonctionnera, vous devrez toujours faire quelques tests à partir d'un\némulateur ou à partir du système d'exploitation final.",[13,13011,13012,13013,298],{},"Bref, à partir du moment où vous avez besoin de compiler un programme\npour une autre architecture, ou pour un autre système d'exploitation que\nvotre machine actuelle, vous avez besoin de faire de la ",[72,13014,13015],{},"compilation\ncroisée",[133,13017,13019],{"id":13018},"de-quoi-va-parler-ce-billet","De quoi va parler ce billet ?",[13,13021,13022,13023,13026,13027,13035],{},"Ce billet ne va pas parler de la ",[72,13024,13025],{},"compilation croisée"," entre deux\narchitectures différentes, mais uniquement de la compilation croisée à\ndestination d'une machine Windows à partir d'une machine Linux. La\ncompilation croisée entre architectures pourra être vue dans un\nfutur",[97,13028,13029],{},[23,13030,13034],{"href":13031,"ariaDescribedBy":13032,"dataFootnoteRef":104,"id":13033},"#user-content-fn-4",[103],"user-content-fnref-4","4"," article, ou sur d'autres sites.",[13,13037,13038],{},"Afin de pouvoir faire de la compilation croisée, il vous faudra\ninstaller les outils suivants :",[48,13040,13041,13052],{},[51,13042,13043,13044,13051],{},"MinGW",[97,13045,13046],{},[23,13047,6500],{"href":13048,"ariaDescribedBy":13049,"dataFootnoteRef":104,"id":13050},"#user-content-fn-5",[103],"user-content-fnref-5"," : utilisé en tant que cross-compilateur, il nous\ngénèrera un exécutable Windows.",[51,13053,13054,13055,13063],{},"Wine",[97,13056,13057],{},[23,13058,13062],{"href":13059,"ariaDescribedBy":13060,"dataFootnoteRef":104,"id":13061},"#user-content-fn-6",[103],"user-content-fnref-6","6"," : Qui nous servira à vérifier l'exécutable créé.",[13,13065,13066],{},"Les différentes étapes de la constitution de ce billet seront :",[48,13068,13069,13072,13075],{},[51,13070,13071],{},"Installation des outils",[51,13073,13074],{},"Compilation d'un programme simple",[51,13076,13077],{},"Compilation d'un programme Qt simple",[133,13079,13071],{"id":13080},"installation-des-outils",[13,13082,13083,13084,13087,13088,13091,13099],{},"Nous y sommes :). Nous allons commencer par installer les outils qui\nnous permettront de faire de la compilation croisée. Sous la\ndistribution de votre choix, il vous faudra donc installer ",[151,13085,13086],{},"mingw"," ainsi\nque ",[151,13089,13090],{},"wine",[97,13092,13093],{},[23,13094,13098],{"href":13095,"ariaDescribedBy":13096,"dataFootnoteRef":104,"id":13097},"#user-content-fn-7",[103],"user-content-fnref-7","7",". Sous une Gnu\u002FDebian, on pourra par exemple faire :",[145,13101,13103],{"className":8456,"code":13102,"language":8458,"meta":104,"style":104},"> sudo aptitude install mingw32-runtime wine\n",[151,13104,13105],{"__ignoreMap":104},[154,13106,13107],{"class":156,"line":157},[154,13108,13102],{"class":174},[13,13110,13111],{},"Pour vérifier la version actuelle de mingw vous pouvez faire :",[145,13113,13115],{"className":8456,"code":13114,"language":8458,"meta":104,"style":104},"> i586-mingw32msvc-gcc --version\ni586-mingw32msvc-gcc (GCC) 4.2.1-sjlj (mingw32-2)\nCopyright (C) 2007 Free Software Foundation, Inc.\nThis is free software; see the source for copying conditions.  There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",[151,13116,13117,13122,13135,13143,13186],{"__ignoreMap":104},[154,13118,13119],{"class":156,"line":157},[154,13120,13121],{"class":174},"> i586-mingw32msvc-gcc --version\n",[154,13123,13124,13127,13130,13133],{"class":156,"line":178},[154,13125,13126],{"class":348},"i586-mingw32msvc-gcc",[154,13128,13129],{"class":174}," (GCC) 4.2.1-sjlj (",[154,13131,13132],{"class":348},"mingw32-2",[154,13134,447],{"class":174},[154,13136,13137,13140],{"class":156,"line":208},[154,13138,13139],{"class":348},"Copyright",[154,13141,13142],{"class":174}," (C) 2007 Free Software Foundation, Inc.\n",[154,13144,13145,13148,13151,13154,13157,13160,13163,13166,13169,13172,13175,13178,13181,13183],{"class":156,"line":215},[154,13146,13147],{"class":348},"This",[154,13149,13150],{"class":201}," is",[154,13152,13153],{"class":201}," free",[154,13155,13156],{"class":201}," software",[154,13158,13159],{"class":174},"; ",[154,13161,13162],{"class":348},"see",[154,13164,13165],{"class":201}," the",[154,13167,13168],{"class":201}," source",[154,13170,13171],{"class":201}," for",[154,13173,13174],{"class":201}," copying",[154,13176,13177],{"class":201}," conditions.",[154,13179,13180],{"class":201},"  There",[154,13182,13150],{"class":201},[154,13184,13185],{"class":201}," NO\n",[154,13187,13188,13191,13193,13196,13199,13201,13204,13207,13210,13213,13216,13219],{"class":156,"line":234},[154,13189,13190],{"class":348},"warranty",[154,13192,13159],{"class":174},[154,13194,13195],{"class":348},"not",[154,13197,13198],{"class":201}," even",[154,13200,13171],{"class":201},[154,13202,13203],{"class":201}," MERCHANTABILITY",[154,13205,13206],{"class":201}," or",[154,13208,13209],{"class":201}," FITNESS",[154,13211,13212],{"class":201}," FOR",[154,13214,13215],{"class":201}," A",[154,13217,13218],{"class":201}," PARTICULAR",[154,13220,13221],{"class":201}," PURPOSE.\n",[133,13223,13074],{"id":13224},"compilation-dun-programme-simple",[13,13226,13227],{},"Commençons par le programme le plus simple du monde:",[145,13229,13231],{"className":8599,"code":13230,"language":8601,"meta":104,"style":104},"#include \u003Ciostream>\n\nint main(int argc, char** argv)\n{\n    std::cout \u003C\u003C \"Hello\" \u003C\u003C std::endl;\n    return 0;\n}\n",[151,13232,13233,13241,13245,13269,13273,13290,13298],{"__ignoreMap":104},[154,13234,13235,13238],{"class":156,"line":157},[154,13236,13237],{"class":160},"#include",[154,13239,13240],{"class":201}," \u003Ciostream>\n",[154,13242,13243],{"class":156,"line":178},[154,13244,212],{"emptyLinePlaceholder":211},[154,13246,13247,13249,13252,13254,13256,13259,13261,13264,13267],{"class":156,"line":208},[154,13248,1954],{"class":160},[154,13250,13251],{"class":348}," main",[154,13253,352],{"class":174},[154,13255,1954],{"class":160},[154,13257,13258],{"class":358}," argc",[154,13260,2877],{"class":174},[154,13262,13263],{"class":160},"char**",[154,13265,13266],{"class":358}," argv",[154,13268,447],{"class":174},[154,13270,13271],{"class":156,"line":215},[154,13272,4306],{"class":174},[154,13274,13275,13278,13281,13284,13287],{"class":156,"line":234},[154,13276,13277],{"class":174},"    std::cout ",[154,13279,13280],{"class":160},"\u003C\u003C",[154,13282,13283],{"class":201}," \"Hello\"",[154,13285,13286],{"class":160}," \u003C\u003C",[154,13288,13289],{"class":174}," std::endl;\n",[154,13291,13292,13294,13296],{"class":156,"line":256},[154,13293,11302],{"class":160},[154,13295,2284],{"class":228},[154,13297,205],{"class":174},[154,13299,13300],{"class":156,"line":374},[154,13301,4329],{"class":174},[13,13303,13304],{},"Puis compilons :",[145,13306,13308],{"className":8456,"code":13307,"language":8458,"meta":104,"style":104},"> i586-mingw32msvc-g++ -o test.exe test.cpp\n> ls\ntest.c\ntest.exe\n> file test.exe\ntest.exe: PE32 executable (console) Intel 80386, for MS Windows\n",[151,13309,13310,13315,13320,13328,13335,13340],{"__ignoreMap":104},[154,13311,13312],{"class":156,"line":157},[154,13313,13314],{"class":174},"> i586-mingw32msvc-g++ -o test.exe test.cpp\n",[154,13316,13317],{"class":156,"line":178},[154,13318,13319],{"class":174},"> ls\n",[154,13321,13322,13325],{"class":156,"line":208},[154,13323,13324],{"class":197},"test",[154,13326,13327],{"class":201},".c\n",[154,13329,13330,13332],{"class":156,"line":215},[154,13331,13324],{"class":197},[154,13333,13334],{"class":201},".exe\n",[154,13336,13337],{"class":156,"line":234},[154,13338,13339],{"class":174},"> file test.exe\n",[154,13341,13342,13344,13347,13350,13353,13356,13359],{"class":156,"line":256},[154,13343,13324],{"class":197},[154,13345,13346],{"class":201},".exe:",[154,13348,13349],{"class":201}," PE32",[154,13351,13352],{"class":201}," executable",[154,13354,13355],{"class":174}," (console) Intel 80386, ",[154,13357,13358],{"class":160},"for",[154,13360,13361],{"class":174}," MS Windows\n",[13,13363,13364],{},"Voilà nous avons donc un programme à destination de Windows. Il ne nous\nreste plus qu'à le tester :",[145,13366,13368],{"className":8456,"code":13367,"language":8458,"meta":104,"style":104},"> wine .\u002Ftest.exe\nHello\n",[151,13369,13370,13375],{"__ignoreMap":104},[154,13371,13372],{"class":156,"line":157},[154,13373,13374],{"class":174},"> wine .\u002Ftest.exe\n",[154,13376,13377],{"class":156,"line":178},[154,13378,13379],{"class":348},"Hello\n",[13,13381,13382],{},"Et voilà, nous avons écrit un petit programme Windows, et nous l'avons\ntesté à l'aide de Wine. Généralement, on utilise l'utilisation de\nMakefile, voir même des générateurs de Makefile. Nous allons compléter\nl'exemple avec CMake. Voici donc un exemple de fichier CMake :",[145,13384,13387],{"className":13385,"code":13386,"language":9066,"meta":104,"style":104},"language-cmake shiki shiki-themes one-dark-pro","project(test)\nadd_executable(test test.cpp)\n",[151,13388,13389,13400],{"__ignoreMap":104},[154,13390,13391,13394,13396,13398],{"class":156,"line":157},[154,13392,13393],{"class":160},"project",[154,13395,352],{"class":174},[154,13397,13324],{"class":160},[154,13399,447],{"class":174},[154,13401,13402,13405,13407,13409,13412],{"class":156,"line":178},[154,13403,13404],{"class":160},"add_executable",[154,13406,352],{"class":174},[154,13408,13324],{"class":160},[154,13410,13411],{"class":160}," test",[154,13413,13414],{"class":174},".cpp)\n",[13,13416,13417],{},"Nous allons donc lancer la compilation, sous Linux :",[145,13419,13421],{"className":8456,"code":13420,"language":8458,"meta":104,"style":104},"> mkdir build\n> cd build\n> cmake ..\u002F\n-- The C compiler identification is GNU\n-- The CXX compiler identification is GNU\n-- Check for working C compiler: \u002Fusr\u002Flib\u002Fccache\u002Fgcc\n-- Check for working C compiler: \u002Fusr\u002Flib\u002Fccache\u002Fgcc -- works\n-- Detecting C compiler ABI info\n-- Detecting C compiler ABI info - done\n-- Check for working CXX compiler: \u002Fusr\u002Flib\u002Fccache\u002Fc++\n-- Check for working CXX compiler: \u002Fusr\u002Flib\u002Fccache\u002Fc++ -- works\n-- Detecting CXX compiler ABI info\n-- Detecting CXX compiler ABI info - done\n-- Configuring done\n-- Generating done\n-- Build files have been written to: \u002Ftmp\u002Fbuild\n> make\nScanning dependencies of target test\n[100%] Building CXX object CMakeFiles\u002Ftest.dir\u002Ftest.cpp.o\nLinking CXX executable test\n\n[100%] Built target test\n> file test\ntest: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU\u002FLinux 2.6.26, BuildID[sha1]=0xf17337fcecb8f3b6ed589d8dce8978be08f2caca, not stripped\n",[151,13422,13423,13428,13433,13438,13460,13477,13497,13520,13537,13558,13575,13596,13610,13628,13637,13646,13670,13675,13691,13696,13707,13711,13716,13721],{"__ignoreMap":104},[154,13424,13425],{"class":156,"line":157},[154,13426,13427],{"class":174},"> mkdir build\n",[154,13429,13430],{"class":156,"line":178},[154,13431,13432],{"class":174},"> cd build\n",[154,13434,13435],{"class":156,"line":208},[154,13436,13437],{"class":174},"> cmake ..\u002F\n",[154,13439,13440,13443,13446,13449,13452,13455,13457],{"class":156,"line":215},[154,13441,13442],{"class":348},"--",[154,13444,13445],{"class":201}," The",[154,13447,13448],{"class":201}," C",[154,13450,13451],{"class":201}," compiler",[154,13453,13454],{"class":201}," identification",[154,13456,13150],{"class":201},[154,13458,13459],{"class":201}," GNU\n",[154,13461,13462,13464,13466,13469,13471,13473,13475],{"class":156,"line":234},[154,13463,13442],{"class":348},[154,13465,13445],{"class":201},[154,13467,13468],{"class":201}," CXX",[154,13470,13451],{"class":201},[154,13472,13454],{"class":201},[154,13474,13150],{"class":201},[154,13476,13459],{"class":201},[154,13478,13479,13481,13484,13486,13489,13491,13494],{"class":156,"line":256},[154,13480,13442],{"class":348},[154,13482,13483],{"class":201}," Check",[154,13485,13171],{"class":201},[154,13487,13488],{"class":201}," working",[154,13490,13448],{"class":201},[154,13492,13493],{"class":201}," compiler:",[154,13495,13496],{"class":201}," \u002Fusr\u002Flib\u002Fccache\u002Fgcc\n",[154,13498,13499,13501,13503,13505,13507,13509,13511,13514,13517],{"class":156,"line":374},[154,13500,13442],{"class":348},[154,13502,13483],{"class":201},[154,13504,13171],{"class":201},[154,13506,13488],{"class":201},[154,13508,13448],{"class":201},[154,13510,13493],{"class":201},[154,13512,13513],{"class":201}," \u002Fusr\u002Flib\u002Fccache\u002Fgcc",[154,13515,13516],{"class":228}," --",[154,13518,13519],{"class":201}," works\n",[154,13521,13522,13524,13527,13529,13531,13534],{"class":156,"line":402},[154,13523,13442],{"class":348},[154,13525,13526],{"class":201}," Detecting",[154,13528,13448],{"class":201},[154,13530,13451],{"class":201},[154,13532,13533],{"class":201}," ABI",[154,13535,13536],{"class":201}," info\n",[154,13538,13539,13541,13543,13545,13547,13549,13552,13555],{"class":156,"line":427},[154,13540,13442],{"class":348},[154,13542,13526],{"class":201},[154,13544,13448],{"class":201},[154,13546,13451],{"class":201},[154,13548,13533],{"class":201},[154,13550,13551],{"class":201}," info",[154,13553,13554],{"class":201}," -",[154,13556,13557],{"class":201}," done\n",[154,13559,13560,13562,13564,13566,13568,13570,13572],{"class":156,"line":450},[154,13561,13442],{"class":348},[154,13563,13483],{"class":201},[154,13565,13171],{"class":201},[154,13567,13488],{"class":201},[154,13569,13468],{"class":201},[154,13571,13493],{"class":201},[154,13573,13574],{"class":201}," \u002Fusr\u002Flib\u002Fccache\u002Fc++\n",[154,13576,13577,13579,13581,13583,13585,13587,13589,13592,13594],{"class":156,"line":458},[154,13578,13442],{"class":348},[154,13580,13483],{"class":201},[154,13582,13171],{"class":201},[154,13584,13488],{"class":201},[154,13586,13468],{"class":201},[154,13588,13493],{"class":201},[154,13590,13591],{"class":201}," \u002Fusr\u002Flib\u002Fccache\u002Fc++",[154,13593,13516],{"class":228},[154,13595,13519],{"class":201},[154,13597,13598,13600,13602,13604,13606,13608],{"class":156,"line":463},[154,13599,13442],{"class":348},[154,13601,13526],{"class":201},[154,13603,13468],{"class":201},[154,13605,13451],{"class":201},[154,13607,13533],{"class":201},[154,13609,13536],{"class":201},[154,13611,13612,13614,13616,13618,13620,13622,13624,13626],{"class":156,"line":485},[154,13613,13442],{"class":348},[154,13615,13526],{"class":201},[154,13617,13468],{"class":201},[154,13619,13451],{"class":201},[154,13621,13533],{"class":201},[154,13623,13551],{"class":201},[154,13625,13554],{"class":201},[154,13627,13557],{"class":201},[154,13629,13630,13632,13635],{"class":156,"line":499},[154,13631,13442],{"class":348},[154,13633,13634],{"class":201}," Configuring",[154,13636,13557],{"class":201},[154,13638,13639,13641,13644],{"class":156,"line":504},[154,13640,13442],{"class":348},[154,13642,13643],{"class":201}," Generating",[154,13645,13557],{"class":201},[154,13647,13648,13650,13652,13655,13658,13661,13664,13667],{"class":156,"line":509},[154,13649,13442],{"class":348},[154,13651,3406],{"class":201},[154,13653,13654],{"class":201}," files",[154,13656,13657],{"class":201}," have",[154,13659,13660],{"class":201}," been",[154,13662,13663],{"class":201}," written",[154,13665,13666],{"class":201}," to:",[154,13668,13669],{"class":201}," \u002Ftmp\u002Fbuild\n",[154,13671,13672],{"class":156,"line":526},[154,13673,13674],{"class":174},"> make\n",[154,13676,13677,13680,13683,13685,13688],{"class":156,"line":549},[154,13678,13679],{"class":348},"Scanning",[154,13681,13682],{"class":201}," dependencies",[154,13684,4763],{"class":201},[154,13686,13687],{"class":201}," target",[154,13689,13690],{"class":201}," test\n",[154,13692,13693],{"class":156,"line":563},[154,13694,13695],{"class":174},"[100%] Building CXX object CMakeFiles\u002Ftest.dir\u002Ftest.cpp.o\n",[154,13697,13698,13701,13703,13705],{"class":156,"line":569},[154,13699,13700],{"class":348},"Linking",[154,13702,13468],{"class":201},[154,13704,13352],{"class":201},[154,13706,13690],{"class":201},[154,13708,13709],{"class":156,"line":786},[154,13710,212],{"emptyLinePlaceholder":211},[154,13712,13713],{"class":156,"line":797},[154,13714,13715],{"class":174},"[100%] Built target test\n",[154,13717,13718],{"class":156,"line":818},[154,13719,13720],{"class":174},"> file test\n",[154,13722,13723,13725,13727,13730,13733,13736,13739,13742,13745,13748,13750,13753,13756,13759,13762,13764,13766],{"class":156,"line":838},[154,13724,13324],{"class":197},[154,13726,143],{"class":201},[154,13728,13729],{"class":201}," ELF",[154,13731,13732],{"class":201}," 32-bit",[154,13734,13735],{"class":201}," LSB",[154,13737,13738],{"class":201}," executable,",[154,13740,13741],{"class":201}," Intel",[154,13743,13744],{"class":201}," 80386,",[154,13746,13747],{"class":201}," version",[154,13749,3559],{"class":228},[154,13751,13752],{"class":174}," (SYSV), dynamically linked (",[154,13754,13755],{"class":348},"uses",[154,13757,13758],{"class":201}," shared",[154,13760,13761],{"class":201}," libs",[154,13763,9498],{"class":174},[154,13765,13358],{"class":160},[154,13767,13768],{"class":174}," GNU\u002FLinux 2.6.26, BuildID[sha1]=0xf17337fcecb8f3b6ed589d8dce8978be08f2caca, not stripped\n",[13,13770,13771],{},"Nous avons donc un binaire pour Gnu\u002FLinux. Recommençons donc mais avec\nWindows :",[145,13773,13775],{"className":8456,"code":13774,"language":8458,"meta":104,"style":104},"> cmake -DCMAKE_C_COMPILER=i586-mingw32msvc-gcc -DCMAKE_CXX_COMPILER=i586-mingw32msvc-g++ ..\u002F\n-- The C compiler identification is GNU\n-- The CXX compiler identification is GNU\n-- Check for working C compiler: \u002Fusr\u002Flib\u002Fccache\u002Fi586-mingw32msvc-gcc\n-- Check for working C compiler: \u002Fusr\u002Flib\u002Fccache\u002Fi586-mingw32msvc-gcc -- works\n-- Detecting C compiler ABI info\n-- Detecting C compiler ABI info - done\n-- Check for working CXX compiler: \u002Fusr\u002Flib\u002Fccache\u002Fi586-mingw32msvc-g++\n-- Check for working CXX compiler: \u002Fusr\u002Flib\u002Fccache\u002Fi586-mingw32msvc-g++ -- works\n-- Detecting CXX compiler ABI info\n-- Detecting CXX compiler ABI info - done\n-- Configuring done\n-- Generating done\n-- Build files have been written to: \u002Ftmp\u002Fbuild-windows\n> make\nScanning dependencies of target test\n[100%] Building CXX object CMakeFiles\u002Ftest.dir\u002Ftest.cpp.o\n\nLinking CXX executable test\n[100%] Built target test\n> file test\ntest: PE32 executable (console) Intel 80386, for MS Windows\n",[151,13776,13777,13803,13819,13835,13852,13873,13887,13905,13922,13943,13957,13975,13983,13991,14010,14014,14026,14030,14034,14044,14048,14052],{"__ignoreMap":104},[154,13778,13779,13782,13785,13787,13789,13792,13794,13797,13800],{"class":156,"line":157},[154,13780,13781],{"class":174},"> cmake ",[154,13783,13784],{"class":193},"-DCMAKE_C_COMPILER",[154,13786,198],{"class":197},[154,13788,13126],{"class":201},[154,13790,13791],{"class":193}," -DCMAKE_CXX_COMPILER",[154,13793,198],{"class":197},[154,13795,13796],{"class":201},"i586-mingw32msvc-g++",[154,13798,13799],{"class":197}," .",[154,13801,13802],{"class":201},".\u002F\n",[154,13804,13805,13807,13809,13811,13813,13815,13817],{"class":156,"line":178},[154,13806,13442],{"class":348},[154,13808,13445],{"class":201},[154,13810,13448],{"class":201},[154,13812,13451],{"class":201},[154,13814,13454],{"class":201},[154,13816,13150],{"class":201},[154,13818,13459],{"class":201},[154,13820,13821,13823,13825,13827,13829,13831,13833],{"class":156,"line":208},[154,13822,13442],{"class":348},[154,13824,13445],{"class":201},[154,13826,13468],{"class":201},[154,13828,13451],{"class":201},[154,13830,13454],{"class":201},[154,13832,13150],{"class":201},[154,13834,13459],{"class":201},[154,13836,13837,13839,13841,13843,13845,13847,13849],{"class":156,"line":215},[154,13838,13442],{"class":348},[154,13840,13483],{"class":201},[154,13842,13171],{"class":201},[154,13844,13488],{"class":201},[154,13846,13448],{"class":201},[154,13848,13493],{"class":201},[154,13850,13851],{"class":201}," \u002Fusr\u002Flib\u002Fccache\u002Fi586-mingw32msvc-gcc\n",[154,13853,13854,13856,13858,13860,13862,13864,13866,13869,13871],{"class":156,"line":234},[154,13855,13442],{"class":348},[154,13857,13483],{"class":201},[154,13859,13171],{"class":201},[154,13861,13488],{"class":201},[154,13863,13448],{"class":201},[154,13865,13493],{"class":201},[154,13867,13868],{"class":201}," \u002Fusr\u002Flib\u002Fccache\u002Fi586-mingw32msvc-gcc",[154,13870,13516],{"class":228},[154,13872,13519],{"class":201},[154,13874,13875,13877,13879,13881,13883,13885],{"class":156,"line":256},[154,13876,13442],{"class":348},[154,13878,13526],{"class":201},[154,13880,13448],{"class":201},[154,13882,13451],{"class":201},[154,13884,13533],{"class":201},[154,13886,13536],{"class":201},[154,13888,13889,13891,13893,13895,13897,13899,13901,13903],{"class":156,"line":374},[154,13890,13442],{"class":348},[154,13892,13526],{"class":201},[154,13894,13448],{"class":201},[154,13896,13451],{"class":201},[154,13898,13533],{"class":201},[154,13900,13551],{"class":201},[154,13902,13554],{"class":201},[154,13904,13557],{"class":201},[154,13906,13907,13909,13911,13913,13915,13917,13919],{"class":156,"line":402},[154,13908,13442],{"class":348},[154,13910,13483],{"class":201},[154,13912,13171],{"class":201},[154,13914,13488],{"class":201},[154,13916,13468],{"class":201},[154,13918,13493],{"class":201},[154,13920,13921],{"class":201}," \u002Fusr\u002Flib\u002Fccache\u002Fi586-mingw32msvc-g++\n",[154,13923,13924,13926,13928,13930,13932,13934,13936,13939,13941],{"class":156,"line":427},[154,13925,13442],{"class":348},[154,13927,13483],{"class":201},[154,13929,13171],{"class":201},[154,13931,13488],{"class":201},[154,13933,13468],{"class":201},[154,13935,13493],{"class":201},[154,13937,13938],{"class":201}," \u002Fusr\u002Flib\u002Fccache\u002Fi586-mingw32msvc-g++",[154,13940,13516],{"class":228},[154,13942,13519],{"class":201},[154,13944,13945,13947,13949,13951,13953,13955],{"class":156,"line":450},[154,13946,13442],{"class":348},[154,13948,13526],{"class":201},[154,13950,13468],{"class":201},[154,13952,13451],{"class":201},[154,13954,13533],{"class":201},[154,13956,13536],{"class":201},[154,13958,13959,13961,13963,13965,13967,13969,13971,13973],{"class":156,"line":458},[154,13960,13442],{"class":348},[154,13962,13526],{"class":201},[154,13964,13468],{"class":201},[154,13966,13451],{"class":201},[154,13968,13533],{"class":201},[154,13970,13551],{"class":201},[154,13972,13554],{"class":201},[154,13974,13557],{"class":201},[154,13976,13977,13979,13981],{"class":156,"line":463},[154,13978,13442],{"class":348},[154,13980,13634],{"class":201},[154,13982,13557],{"class":201},[154,13984,13985,13987,13989],{"class":156,"line":485},[154,13986,13442],{"class":348},[154,13988,13643],{"class":201},[154,13990,13557],{"class":201},[154,13992,13993,13995,13997,13999,14001,14003,14005,14007],{"class":156,"line":499},[154,13994,13442],{"class":348},[154,13996,3406],{"class":201},[154,13998,13654],{"class":201},[154,14000,13657],{"class":201},[154,14002,13660],{"class":201},[154,14004,13663],{"class":201},[154,14006,13666],{"class":201},[154,14008,14009],{"class":201}," \u002Ftmp\u002Fbuild-windows\n",[154,14011,14012],{"class":156,"line":504},[154,14013,13674],{"class":174},[154,14015,14016,14018,14020,14022,14024],{"class":156,"line":509},[154,14017,13679],{"class":348},[154,14019,13682],{"class":201},[154,14021,4763],{"class":201},[154,14023,13687],{"class":201},[154,14025,13690],{"class":201},[154,14027,14028],{"class":156,"line":526},[154,14029,13695],{"class":174},[154,14031,14032],{"class":156,"line":549},[154,14033,212],{"emptyLinePlaceholder":211},[154,14035,14036,14038,14040,14042],{"class":156,"line":563},[154,14037,13700],{"class":348},[154,14039,13468],{"class":201},[154,14041,13352],{"class":201},[154,14043,13690],{"class":201},[154,14045,14046],{"class":156,"line":569},[154,14047,13715],{"class":174},[154,14049,14050],{"class":156,"line":786},[154,14051,13720],{"class":174},[154,14053,14054,14056,14058,14060,14062,14064,14066],{"class":156,"line":797},[154,14055,13324],{"class":197},[154,14057,143],{"class":201},[154,14059,13349],{"class":201},[154,14061,13352],{"class":201},[154,14063,13355],{"class":174},[154,14065,13358],{"class":160},[154,14067,13361],{"class":174},[13,14069,14070],{},"Nous avons donc maintenant la possibilité de compiler notre application\nmulti-platformes depuis Linux pour les systèmes Linux, mais aussi pour\nles systèmes Windows.",[133,14072,14074],{"id":14073},"compilation-dun-programme-écrit-avec-qt","Compilation d'un programme écrit avec Qt",[13,14076,14077],{},"Nous allons maintenant nous compliquer un peu la tâche en compilant un\nprogramme ayant une dépendance avec une librairie externe : Qt. Qt est\nun framework proposant une boîte à outil de classe permettant de faire\ndes interfaces graphiques mais aussi de faire des applications consoles\nrapidement.",[13,14079,14080],{},"Nous allons donc écrire le petit programme suivant, qui affiche une\nboîte de dialogue inutile, avec un bouton inutile :",[145,14082,14084],{"className":8599,"code":14083,"language":8601,"meta":104,"style":104},"#include \u003CQApplication>\n#include \u003CQPushButton>\n\nint main(int argc, char** argv)\n{\n    QApplication app(argc, argv);\n    QPushButton * btn = new QPushButton(\"Do nothing\");\n    btn->show();\n\n    return app.exec();\n}\n",[151,14085,14086,14093,14100,14104,14124,14128,14139,14163,14175,14179,14193],{"__ignoreMap":104},[154,14087,14088,14090],{"class":156,"line":157},[154,14089,13237],{"class":160},[154,14091,14092],{"class":201}," \u003CQApplication>\n",[154,14094,14095,14097],{"class":156,"line":178},[154,14096,13237],{"class":160},[154,14098,14099],{"class":201}," \u003CQPushButton>\n",[154,14101,14102],{"class":156,"line":208},[154,14103,212],{"emptyLinePlaceholder":211},[154,14105,14106,14108,14110,14112,14114,14116,14118,14120,14122],{"class":156,"line":215},[154,14107,1954],{"class":160},[154,14109,13251],{"class":348},[154,14111,352],{"class":174},[154,14113,1954],{"class":160},[154,14115,13258],{"class":358},[154,14117,2877],{"class":174},[154,14119,13263],{"class":160},[154,14121,13266],{"class":358},[154,14123,447],{"class":174},[154,14125,14126],{"class":156,"line":234},[154,14127,4306],{"class":174},[154,14129,14130,14133,14136],{"class":156,"line":256},[154,14131,14132],{"class":174},"    QApplication ",[154,14134,14135],{"class":348},"app",[154,14137,14138],{"class":174},"(argc, argv);\n",[154,14140,14141,14144,14146,14149,14151,14153,14156,14158,14161],{"class":156,"line":374},[154,14142,14143],{"class":174},"    QPushButton ",[154,14145,4584],{"class":160},[154,14147,14148],{"class":174}," btn ",[154,14150,198],{"class":160},[154,14152,312],{"class":160},[154,14154,14155],{"class":348}," QPushButton",[154,14157,352],{"class":174},[154,14159,14160],{"class":201},"\"Do nothing\"",[154,14162,362],{"class":174},[154,14164,14165,14168,14170,14173],{"class":156,"line":402},[154,14166,14167],{"class":164},"    btn",[154,14169,1535],{"class":174},[154,14171,14172],{"class":348},"show",[154,14174,610],{"class":174},[154,14176,14177],{"class":156,"line":427},[154,14178,212],{"emptyLinePlaceholder":211},[154,14180,14181,14183,14186,14188,14191],{"class":156,"line":450},[154,14182,11302],{"class":160},[154,14184,14185],{"class":164}," app",[154,14187,298],{"class":174},[154,14189,14190],{"class":348},"exec",[154,14192,610],{"class":174},[154,14194,14195],{"class":156,"line":458},[154,14196,4329],{"class":174},[13,14198,14199,14200,14203],{},"Avec le fichier ",[151,14201,14202],{},"qmake"," associé tout simple :",[145,14205,14209],{"className":14206,"code":14207,"language":14208,"meta":104,"style":104},"language-ini shiki shiki-themes one-dark-pro","[qmake]\nTEMPLATE = app\nTARGET =\nDEPENDPATH += .\nINCLUDEPATH += .\n\n# Input\nSOURCES += test.cpp\n","ini",[151,14210,14211,14216,14226,14234,14239,14244,14248,14253],{"__ignoreMap":104},[154,14212,14213],{"class":156,"line":157},[154,14214,14215],{"class":348},"[qmake]\n",[154,14217,14218,14221,14223],{"class":156,"line":178},[154,14219,14220],{"class":160},"TEMPLATE",[154,14222,2968],{"class":174},[154,14224,14225],{"class":201}," app\n",[154,14227,14228,14231],{"class":156,"line":208},[154,14229,14230],{"class":160},"TARGET",[154,14232,14233],{"class":174}," =\n",[154,14235,14236],{"class":156,"line":215},[154,14237,14238],{"class":201},"DEPENDPATH += .\n",[154,14240,14241],{"class":156,"line":234},[154,14242,14243],{"class":201},"INCLUDEPATH += .\n",[154,14245,14246],{"class":156,"line":256},[154,14247,212],{"emptyLinePlaceholder":211},[154,14249,14250],{"class":156,"line":374},[154,14251,14252],{"class":647},"# Input\n",[154,14254,14255],{"class":156,"line":402},[154,14256,14257],{"class":201},"SOURCES += test.cpp\n",[13,14259,14260,14261,14264],{},"On test la compilation à l'aide de ",[151,14262,14263],{},"qmake ; make"," et on lance le\nprogramme :",[13,14266,14267],{},[123,14268],{"alt":14269,"src":14270},"cross-compil1","\u002FProgrammation\u002Fcross-compilation-compiler-un-programme-pour-ms-windows-sous-gnu-linux\u002Fcross-compil1.png",[13,14272,14273],{},"Maintenant que notre programme compile et fonctionne sous Gnu\u002FLinux,\nnous allons pouvoir faire le même test mais en compilant une version\nWindows. Pour cela, il va nous falloir la version Windows de Qt (nous\nn'allons pas compiler Qt, alors que la librairie existe déjà).",[13,14275,14276,14277,14281],{},"Vous pouvez commencer par télécharger la dernière version de Qt (ou\ncelle qui vous convient) à l'adresse suivante\n",[23,14278,14279],{"href":14279,"rel":14280},"http:\u002F\u002Fqt.nokia.com\u002Fproducts\u002F",[27]," et l'installer. Vous n'avez pas besoin\nde MinGW, ni de QtCreator. Vous pouvez donc télécharger directement la\nversion qui ne contient que la librairie.",[13,14283,14284],{},[123,14285],{"alt":14286,"src":14287},"cross-compil3","\u002FProgrammation\u002Fcross-compilation-compiler-un-programme-pour-ms-windows-sous-gnu-linux\u002Fcross-compil3.png",[13,14289,14290],{},"Si à l'installation, l'application demande l'installation ou\nl'emplacement de MinGW, vous n'avez pas besoin de le renseigner, nous\nutiliserons la version Linux de MinGW.",[13,14292,14293],{},[123,14294],{"alt":14295,"src":14296},"cross-compil2","\u002FProgrammation\u002Fcross-compilation-compiler-un-programme-pour-ms-windows-sous-gnu-linux\u002Fcross-compil2.png",[13,14298,14299],{},"Enfin nous allons faire un peu de paramétrage. Nous allons récupérer le\ndossier de specs Qt pour windows et l'adapter pour MinGW sous Linux. Les\nadaptations à faire sont :",[48,14301,14302,14305,14308],{},[51,14303,14304],{},"Utilisation de MinGW",[51,14306,14307],{},"Définition des dossiers de Qt Windows et MinGW",[51,14309,14310],{},"Suppression de l'extension .exe",[13,14312,14313],{},"Commençons par créer le fichier qmake.conf :",[145,14315,14317],{"className":8456,"code":14316,"language":8458,"meta":104,"style":104},"sudo cp -Rf \u002Fusr\u002Fshare\u002Fqt4\u002Fmkspecs\u002Fwin32-g++ \u002Fusr\u002Fshare\u002Fqt4\u002Fmkspecs\u002Fwin32-x-g++\nsudo nano \u002Fusr\u002Fshare\u002Fqt4\u002Fmkspecs\u002Fwin32-x-g++\u002Fqmake.conf\n",[151,14318,14319,14336],{"__ignoreMap":104},[154,14320,14321,14324,14327,14330,14333],{"class":156,"line":157},[154,14322,14323],{"class":348},"sudo",[154,14325,14326],{"class":201}," cp",[154,14328,14329],{"class":228}," -Rf",[154,14331,14332],{"class":201}," \u002Fusr\u002Fshare\u002Fqt4\u002Fmkspecs\u002Fwin32-g++",[154,14334,14335],{"class":201}," \u002Fusr\u002Fshare\u002Fqt4\u002Fmkspecs\u002Fwin32-x-g++\n",[154,14337,14338,14340,14343],{"class":156,"line":178},[154,14339,14323],{"class":348},[154,14341,14342],{"class":201}," nano",[154,14344,14345],{"class":201}," \u002Fusr\u002Fshare\u002Fqt4\u002Fmkspecs\u002Fwin32-x-g++\u002Fqmake.conf\n",[13,14347,14348,14349,14357],{},"Voici le fichier de diff",[97,14350,14351],{},[23,14352,14356],{"href":14353,"ariaDescribedBy":14354,"dataFootnoteRef":104,"id":14355},"#user-content-fn-8",[103],"user-content-fnref-8","8"," qui contient les choses à modifier, vous pouvez\nle récupérer et utiliser la commande patch pour reporter les\nmodifications, ou faire les modifications à la mains :",[145,14359,14363],{"className":14360,"code":14361,"language":14362,"meta":104,"style":104},"language-diff shiki shiki-themes one-dark-pro","17c17\n\u003C QMAKE_CC              = gcc\n---\n> QMAKE_CC              = i586-mingw32msvc-gcc\n30c30\n\u003C QMAKE_CXX             = g++\n---\n> QMAKE_CXX             = i586-mingw32msvc-g++\n44,46c44,46\n\u003C QMAKE_INCDIR          =\n\u003C QMAKE_INCDIR_QT               = $$[QT_INSTALL_HEADERS]\n\u003C QMAKE_LIBDIR_QT               = $$[QT_INSTALL_LIBS]\n---\n> QMAKE_INCDIR          = \u002Fusr\u002Fi586-mingw32msvc\u002Finclude\n> QMAKE_INCDIR_QT               = \u002Fhome\u002Fphoenix\u002F.wine\u002Fdrive_c\u002FQt\u002F4.8.2\u002Finclude\n> QMAKE_LIBDIR_QT               = \u002Fhome\u002Fphoenix\u002F.wine\u002Fdrive_c\u002FQt\u002F4.8.2\u002Flib\n53,54c53,54\n\u003C QMAKE_LINK            = g++\n\u003C QMAKE_LINK_C          = gcc\n---\n> QMAKE_LINK            = i586-mingw32msvc-g++\n> QMAKE_LINK_C          = i586-mingw32msvc-gcc\n77c77\n\u003C !isEmpty(QMAKE_SH) {\n---\n> #!isEmpty(QMAKE_SH) {\n88,100c88,100\n\u003C } else {\n\u003C       QMAKE_COPY              = copy \u002Fy\n\u003C       QMAKE_COPY_DIR          = xcopy \u002Fs \u002Fq \u002Fy \u002Fi\n\u003C       QMAKE_MOVE              = move\n\u003C       QMAKE_DEL_FILE          = del\n\u003C       QMAKE_MKDIR             = mkdir\n\u003C       QMAKE_DEL_DIR           = rmdir\n\u003C     QMAKE_CHK_DIR_EXISTS      = if not exist\n\u003C }\n\u003C\n\u003C QMAKE_MOC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}moc.exe\n\u003C QMAKE_UIC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}uic.exe\n\u003C QMAKE_IDC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}idc.exe\n---\n> #} else {\n> #     QMAKE_COPY              = copy \u002Fy\n> #     QMAKE_COPY_DIR          = xcopy \u002Fs \u002Fq \u002Fy \u002Fi\n> #     QMAKE_MOVE              = move\n> #     QMAKE_DEL_FILE          = del\n> #     QMAKE_MKDIR             = mkdir\n> #     QMAKE_DEL_DIR           = rmdir\n> #    QMAKE_CHK_DIR_EXISTS     = if not exist\n> #}\n>\n> QMAKE_MOC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}moc-qt4\n> QMAKE_UIC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}uic-qt4\n> QMAKE_IDC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}idc-qt4\n","diff",[151,14364,14365,14370,14375,14380,14385,14390,14395,14399,14404,14409,14414,14419,14424,14428,14433,14438,14443,14448,14453,14458,14462,14467,14472,14477,14482,14486,14491,14496,14501,14506,14511,14516,14521,14526,14531,14536,14541,14546,14551,14556,14561,14565,14570,14575,14580,14585,14590,14595,14600,14606,14611,14616,14622,14628],{"__ignoreMap":104},[154,14366,14367],{"class":156,"line":157},[154,14368,14369],{"class":174},"17c17\n",[154,14371,14372],{"class":156,"line":178},[154,14373,14374],{"class":193},"\u003C QMAKE_CC              = gcc\n",[154,14376,14377],{"class":156,"line":208},[154,14378,14379],{"class":174},"---\n",[154,14381,14382],{"class":156,"line":215},[154,14383,14384],{"class":201},"> QMAKE_CC              = i586-mingw32msvc-gcc\n",[154,14386,14387],{"class":156,"line":234},[154,14388,14389],{"class":174},"30c30\n",[154,14391,14392],{"class":156,"line":256},[154,14393,14394],{"class":193},"\u003C QMAKE_CXX             = g++\n",[154,14396,14397],{"class":156,"line":374},[154,14398,14379],{"class":174},[154,14400,14401],{"class":156,"line":402},[154,14402,14403],{"class":201},"> QMAKE_CXX             = i586-mingw32msvc-g++\n",[154,14405,14406],{"class":156,"line":427},[154,14407,14408],{"class":174},"44,46c44,46\n",[154,14410,14411],{"class":156,"line":450},[154,14412,14413],{"class":193},"\u003C QMAKE_INCDIR          =\n",[154,14415,14416],{"class":156,"line":458},[154,14417,14418],{"class":193},"\u003C QMAKE_INCDIR_QT               = $$[QT_INSTALL_HEADERS]\n",[154,14420,14421],{"class":156,"line":463},[154,14422,14423],{"class":193},"\u003C QMAKE_LIBDIR_QT               = $$[QT_INSTALL_LIBS]\n",[154,14425,14426],{"class":156,"line":485},[154,14427,14379],{"class":174},[154,14429,14430],{"class":156,"line":499},[154,14431,14432],{"class":201},"> QMAKE_INCDIR          = \u002Fusr\u002Fi586-mingw32msvc\u002Finclude\n",[154,14434,14435],{"class":156,"line":504},[154,14436,14437],{"class":201},"> QMAKE_INCDIR_QT               = \u002Fhome\u002Fphoenix\u002F.wine\u002Fdrive_c\u002FQt\u002F4.8.2\u002Finclude\n",[154,14439,14440],{"class":156,"line":509},[154,14441,14442],{"class":201},"> QMAKE_LIBDIR_QT               = \u002Fhome\u002Fphoenix\u002F.wine\u002Fdrive_c\u002FQt\u002F4.8.2\u002Flib\n",[154,14444,14445],{"class":156,"line":526},[154,14446,14447],{"class":174},"53,54c53,54\n",[154,14449,14450],{"class":156,"line":549},[154,14451,14452],{"class":193},"\u003C QMAKE_LINK            = g++\n",[154,14454,14455],{"class":156,"line":563},[154,14456,14457],{"class":193},"\u003C QMAKE_LINK_C          = gcc\n",[154,14459,14460],{"class":156,"line":569},[154,14461,14379],{"class":174},[154,14463,14464],{"class":156,"line":786},[154,14465,14466],{"class":201},"> QMAKE_LINK            = i586-mingw32msvc-g++\n",[154,14468,14469],{"class":156,"line":797},[154,14470,14471],{"class":201},"> QMAKE_LINK_C          = i586-mingw32msvc-gcc\n",[154,14473,14474],{"class":156,"line":818},[154,14475,14476],{"class":174},"77c77\n",[154,14478,14479],{"class":156,"line":838},[154,14480,14481],{"class":193},"\u003C !isEmpty(QMAKE_SH) {\n",[154,14483,14484],{"class":156,"line":854},[154,14485,14379],{"class":174},[154,14487,14488],{"class":156,"line":882},[154,14489,14490],{"class":201},"> #!isEmpty(QMAKE_SH) {\n",[154,14492,14493],{"class":156,"line":887},[154,14494,14495],{"class":174},"88,100c88,100\n",[154,14497,14498],{"class":156,"line":892},[154,14499,14500],{"class":193},"\u003C } else {\n",[154,14502,14503],{"class":156,"line":899},[154,14504,14505],{"class":193},"\u003C       QMAKE_COPY              = copy \u002Fy\n",[154,14507,14508],{"class":156,"line":910},[154,14509,14510],{"class":193},"\u003C       QMAKE_COPY_DIR          = xcopy \u002Fs \u002Fq \u002Fy \u002Fi\n",[154,14512,14513],{"class":156,"line":921},[154,14514,14515],{"class":193},"\u003C       QMAKE_MOVE              = move\n",[154,14517,14518],{"class":156,"line":931},[154,14519,14520],{"class":193},"\u003C       QMAKE_DEL_FILE          = del\n",[154,14522,14523],{"class":156,"line":946},[154,14524,14525],{"class":193},"\u003C       QMAKE_MKDIR             = mkdir\n",[154,14527,14528],{"class":156,"line":961},[154,14529,14530],{"class":193},"\u003C       QMAKE_DEL_DIR           = rmdir\n",[154,14532,14533],{"class":156,"line":976},[154,14534,14535],{"class":193},"\u003C     QMAKE_CHK_DIR_EXISTS      = if not exist\n",[154,14537,14538],{"class":156,"line":1000},[154,14539,14540],{"class":193},"\u003C }\n",[154,14542,14543],{"class":156,"line":1005},[154,14544,14545],{"class":193},"\u003C\n",[154,14547,14548],{"class":156,"line":5920},[154,14549,14550],{"class":193},"\u003C QMAKE_MOC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}moc.exe\n",[154,14552,14553],{"class":156,"line":5926},[154,14554,14555],{"class":193},"\u003C QMAKE_UIC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}uic.exe\n",[154,14557,14558],{"class":156,"line":8267},[154,14559,14560],{"class":193},"\u003C QMAKE_IDC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}idc.exe\n",[154,14562,14563],{"class":156,"line":8275},[154,14564,14379],{"class":174},[154,14566,14567],{"class":156,"line":8283},[154,14568,14569],{"class":201},"> #} else {\n",[154,14571,14572],{"class":156,"line":8291},[154,14573,14574],{"class":201},"> #     QMAKE_COPY              = copy \u002Fy\n",[154,14576,14577],{"class":156,"line":8299},[154,14578,14579],{"class":201},"> #     QMAKE_COPY_DIR          = xcopy \u002Fs \u002Fq \u002Fy \u002Fi\n",[154,14581,14582],{"class":156,"line":8307},[154,14583,14584],{"class":201},"> #     QMAKE_MOVE              = move\n",[154,14586,14587],{"class":156,"line":8315},[154,14588,14589],{"class":201},"> #     QMAKE_DEL_FILE          = del\n",[154,14591,14592],{"class":156,"line":8326},[154,14593,14594],{"class":201},"> #     QMAKE_MKDIR             = mkdir\n",[154,14596,14597],{"class":156,"line":8334},[154,14598,14599],{"class":201},"> #     QMAKE_DEL_DIR           = rmdir\n",[154,14601,14603],{"class":156,"line":14602},49,[154,14604,14605],{"class":201},"> #    QMAKE_CHK_DIR_EXISTS     = if not exist\n",[154,14607,14608],{"class":156,"line":3},[154,14609,14610],{"class":201},"> #}\n",[154,14612,14614],{"class":156,"line":14613},51,[154,14615,11876],{"class":201},[154,14617,14619],{"class":156,"line":14618},52,[154,14620,14621],{"class":201},"> QMAKE_MOC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}moc-qt4\n",[154,14623,14625],{"class":156,"line":14624},53,[154,14626,14627],{"class":201},"> QMAKE_UIC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}uic-qt4\n",[154,14629,14631],{"class":156,"line":14630},54,[154,14632,14633],{"class":201},"> QMAKE_IDC             = $$[QT_INSTALL_BINS]$${DIR_SEPARATOR}idc-qt4\n",[13,14635,14636],{},"Une fois terminé on peut compiler et lancer l'application de la manière\nsuivante :",[145,14638,14640],{"className":8456,"code":14639,"language":8458,"meta":104,"style":104},"> qmake-qt4 -spec win32-x-g++\n> make\n> wine .\u002Frelease\u002Ftest.exe\n",[151,14641,14642,14647,14651],{"__ignoreMap":104},[154,14643,14644],{"class":156,"line":157},[154,14645,14646],{"class":174},"> qmake-qt4 -spec win32-x-g++\n",[154,14648,14649],{"class":156,"line":178},[154,14650,13674],{"class":174},[154,14652,14653],{"class":156,"line":208},[154,14654,14655],{"class":174},"> wine .\u002Frelease\u002Ftest.exe\n",[13,14657,14658],{},[123,14659],{"alt":14660,"src":14661},"cross-compil4","\u002FProgrammation\u002Fcross-compilation-compiler-un-programme-pour-ms-windows-sous-gnu-linux\u002Fcross-compil4.png",[64,14663,6958],{"id":6957},[13,14665,14666],{},"Et voilà, vous êtes maintenant capable de faire de la compilation\ncroisée pour des programmes aussi simples que complexes :). Quand vos\nprogrammes ont des dépendances, et si vous le pouvez, préférez la\nversion binaire. Sinon vous devrez compiler les librairies vous-même au\nformat Windows de la même manière avant de compiler votre programme.\nCela peut vous obliger à modifier les scriptes de build.",[4118,14668,14670,14673],{"className":14669,"dataFootnotes":104},[4121],[64,14671,4126],{"className":14672,"id":103},[4125],[4128,14674,14675,14681,14687,14695,14704,14717,14726,14735],{},[51,14676,14677,14678],{"id":4132},"En français cela donne compilation croisée ",[23,14679,4140],{"href":4136,"ariaLabel":4137,"className":14680,"dataFootnoteBackref":104},[4139],[51,14682,14683,14684],{"id":4143},"bien que ... ",[23,14685,4140],{"href":4147,"ariaLabel":4148,"className":14686,"dataFootnoteBackref":104},[4139],[51,14688,14690,14691],{"id":14689},"user-content-fn-3","là aussi avec les smartphones actuels, on peut en douter ",[23,14692,4140],{"href":14693,"ariaLabel":6064,"className":14694,"dataFootnoteBackref":104},"#user-content-fnref-3",[4139],[51,14696,14698,14699],{"id":14697},"user-content-fn-4","très lointain ",[23,14700,4140],{"href":14701,"ariaLabel":14702,"className":14703,"dataFootnoteBackref":104},"#user-content-fnref-4","Back to reference 4",[4139],[51,14705,14707,14708,14711,14712],{"id":14706},"user-content-fn-5","MinGW est un ",[72,14709,14710],{},"portage"," de gcc pour Window. Il nous permettra donc de générer un executable Windows à partir de notre Gnu\u002FLinux ",[23,14713,4140],{"href":14714,"ariaLabel":14715,"className":14716,"dataFootnoteBackref":104},"#user-content-fnref-5","Back to reference 5",[4139],[51,14718,14720,14721],{"id":14719},"user-content-fn-6","Un émulateur pour démarrer des programmes Windows sous Linux ",[23,14722,4140],{"href":14723,"ariaLabel":14724,"className":14725,"dataFootnoteBackref":104},"#user-content-fnref-6","Back to reference 6",[4139],[51,14727,14729,14730],{"id":14728},"user-content-fn-7","L'installation de wine sous une distribution 64-bit peut-être un peu plus compliqué que prévu, mais reste néanmoins faisable. Référez vous à la documentation de votre distribution. ",[23,14731,4140],{"href":14732,"ariaLabel":14733,"className":14734,"dataFootnoteBackref":104},"#user-content-fnref-7","Back to reference 7",[4139],[51,14736,14738,14739,14743,14744],{"id":14737},"user-content-fn-8","Vous pouvez aussi le télécharger ",[23,14740,14742],{"href":14741},"\u002FProgrammation\u002Fcross-compilation-compiler-un-programme-pour-ms-windows-sous-gnu-linux\u002Fqmake.conf.diff","ici",". ",[23,14745,4140],{"href":14746,"ariaLabel":14747,"className":14748,"dataFootnoteBackref":104},"#user-content-fnref-8","Back to reference 8",[4139],[4151,14750,14751],{},"html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html pre.shiki code .seHd6, html code.shiki .seHd6{--shiki-default:#C678DD}html pre.shiki code .s_ZVi, html code.shiki .s_ZVi{--shiki-default:#E06C75;--shiki-default-font-style:italic}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sU0A5, html code.shiki .sU0A5{--shiki-default:#E5C07B}html pre.shiki code .sV9Aq, html code.shiki .sV9Aq{--shiki-default:#7F848E;--shiki-default-font-style:italic}",{"title":104,"searchDepth":178,"depth":178,"links":14753},[14754,14756,14757,14758,14759,14760,14761],{"id":12964,"depth":208,"text":14755},"Qu'est que la cross-compilation1 ?",{"id":13018,"depth":208,"text":13019},{"id":13080,"depth":208,"text":13071},{"id":13224,"depth":208,"text":13074},{"id":14073,"depth":208,"text":14074},{"id":6957,"depth":178,"text":6958},{"id":103,"depth":178,"text":4126},"2012-07-01",{"type":10,"value":14764},[14765,14772,14774,14776,14778,14798,14800],[133,14766,12965,14767,12971],{"id":12964},[97,14768,14769],{},[23,14770,106],{"href":101,"ariaDescribedBy":14771,"dataFootnoteRef":104,"id":105},[103],[13,14773,12974],{},[13,14775,12977],{},[13,14777,12980],{},[48,14779,14780,14782,14789,14796],{},[51,14781,12985],{},[51,14783,12988,14784,2300],{},[97,14785,14786],{},[23,14787,1447],{"href":1444,"ariaDescribedBy":14788,"dataFootnoteRef":104,"id":1446},[103],[51,14790,12996,14791,2300],{},[97,14792,14793],{},[23,14794,4701],{"href":13001,"ariaDescribedBy":14795,"dataFootnoteRef":104,"id":13003},[103],[51,14797,13006],{},[13,14799,13009],{},[13,14801,13012,14802,298],{},[72,14803,13015],{},{},"\u002Fpost\u002Fcross-compilation-compiler-un-programme-pour-ms-windows-sous-gnu-linux",{"title":12959,"description":104},"cross-compilation-compiler-un-programme-pour-ms-windows-sous-gnu-linux","posts\u002FProgrammation\u002F2012-07-01-cross-compilation-compiler-un-programme-pour-ms-windows-sous-gnu-linux",[9907],"xyp3lTmc_CVoPo48zy-LSh68_42nO_FD4l7WdmhTvoA",1777849582746]