Question

We are migrating from one google apps domain to another. To make my users life easier I want to set up a script they can run which will copy their calendars across. I have the code to copy events, and that works, but I need to then do the code equivalent of "copy to my calendar" option that users get, as we will be closing old google apps domain, and I fear they will lose all these events as they are on an external (deleted) calendar.

So in google apps script - how do you copy a calendar event, to be an actual event in a new domain, not a copied event from old domain?

Hard to explain!

George

No correct solution

OTHER TIPS

One possible way of doing that would be -

Write your App Script in old domain, that is container Bound(Probably a Google Doc) and is deployed to run as trigger, that tuns on a certain frequency (minimum is 1 min). Copy this Google Doc to all your users in old Domain and let them authorize and invoke the Script. This Script would read certain number of events so, that it does not runs into usage limits as described here, then use Calendar APIs in Apps Script to create the Events in New Domain [I have not tried this, but, I think it should be possible as it is based on REST and supports OAuth]. Here is an example of integrating with Twitter APIs and another one with Salesforce APIs

There are limitations to what you can accomplish through Google Apps Script Calendar Services:

  • Ownership & Access: any script will need permissions to access calendars in both your old and your new domains. Generally, this can be addressed by individual users by setting the sharing on their old calendar, so their new UserId has "view" privileges. It's best if their new domain account is used to create their calendar entries, so that they are clearly owners of them.
  • Invitee lists can be copied, but responses to the original invitations won't connect. Given that many of the invitees are also having their email addresses changed in the move, copying invitees is probably not helpful anyway.
  • Some calendar event attributes are not accessible through Calendar Services, and may be lost.
  • Recurrence is a mess. Currently, events with recurrence become CalendarEventSeries, which don't support some methods that events do. There's no way to 'get' recurrence information - so be prepared to see recurring events become (multiple) individual events.
  • There appears to be a bug with all day events - the API seems to work, but when you check the resulting event, you'll find that the "all day" flag isn't checked.

Here's a function that does most of the job of copying calendar events. It's an excerpt from a gist that includes additional code for a spreadsheet menu / user interface and error checking. Visit the gist for more information about installing and using the script.

/**
 * Copy all events in given date range. Returns number of unique events
 * that were copied.
 */
function copyEvents(fromCal, toCal, startDate, endDate) {
  var copiedEvents = [];

  var date = new Date(startDate);
  var endD = new Date(endDate);
  Logger.log("startDate="+startDate+", date="+date+", endDate="+endDate);
  while (date <= endD) {
    Logger.log(date);
    var events = fromCal.getEventsForDay(date);
    for (var event=0; event < events.length; event++) {
      var src = events[event];
      var srcId = src.getId();
      if (copiedEvents.indexOf(srcId) >= 0) continue;  // Multi-day events only need to be copied once
      copiedEvents.push(srcId);
      var newEvent;
      if (src.isAllDayEvent()) {
        newEvent = toCal.createAllDayEvent(src.getTitle(), src.getAllDayStartDate())
                        .setTime(src.getAllDayStartDate(), src.getAllDayEndDate());
      }
      else {
        newEvent = toCal.createEvent(src.getTitle(), src.getStartTime(), src.getEndTime());
      }
      // Set additional attributes, common for all day & single events
      var desc = src.getDescription();
      if (!src.isOwnedByMe()) {   // Prepend Email address of creator to description, if not "me"
        var creators = src.getCreators();
        if (creators.length > 0)
          desc = creators[0].concat(desc);
      }
      try {
        newEvent.setDescription(desc);
      } catch (e) {
        // Work around http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3425
      };
      newEvent.setLocation(src.getLocation());
      // Email Reminders
      var reminders = src.getEmailReminders();
      for (var rem=0; rem < reminders.length; rem++) {
        src.addEmailReminder(reminders[rem]);
      }
      // Popup Reminders
      reminders = src.getPopupReminders();
      for (var rem=0; rem < reminders.length; rem++) {
        src.addPopupReminder(reminders[rem]);
      }
      // SMS Reminders
      reminders = src.getSmsReminders();
      for (var rem=0; rem < reminders.length; rem++) {
        src.addSmsReminder(reminders[rem]);
      }
    }
    date.setDate(date.getDate() + 1);
  }

  return copiedEvents.length;
}

The main issue that you might struggle with is probably due to the relative slowness of the calendar service.

Even if Mogsdad code is well written and probably as efficient as possible it won't be able to copy a big amount of event in one batch because of the 5' time limit.

I've been dealing with calendars for a long time and that has always been a real concern...

So my idea was to handle the process in 2 times, a "backup" that writes all the events into a spreadsheet for storage, that's fairly simple and straightforward... and a "restore" function that reads the spreadsheet and copies the events in a calendar.

The nice trick is that you can copy these calendar data to another calendar and that's where it becomes interesting for your use case ! you can indeed restore all the events in any other domain calendar as long as you have write rights on it.

This script has been used on very large sets of data (more than 3000 events in a one year period) and worked pretty well. I use it to duplicate calendars so I have 'test copies' where I can do all sort of other experiments without touching the 'real ones'.

The script is very long and is not well documented (I didn't write it for this post ;-) but feel free to give it a try by adding it to a spreadsheet. The UI is in french (another bad idea for this forum, sorry about that) but any translator will help you to customize it your way if necessary (if ever you decide to use it). Anyway it uses very simple words and I'm sure anyone would understand it without effort.

Here it is : (please note that you can select more than one calendar in the backup process)

var FUS1=new Date().toString().substr(25,6)+":00";
var tz = SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetTimeZone();



function onOpen() {   
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var menuEntries = [
                      {name: "Backup des agendas", functionName: "backup"},  
                      {name: "Restauration d'un agenda", functionName: "restore"},  
                      ];  
  ss.addMenu("Fonction Tech.", menuEntries);  
}


    function backup() {
      var doc = SpreadsheetApp.getActiveSpreadsheet();
      var app = UiApp.createApplication().setTitle("Sauvegarde des agendas en feuilles Google");
      app.setHeight(325).setWidth(420);
      // Create a grid with 3 text boxes and corresponding labels
      var grid = app.createGrid(4, 2);
      var wait = app.createImage('https://dl.dropboxusercontent.com/u/211279/loading3.gif').setVisible(false);
      grid.setWidget(0, 0, app.createLabel("Nom des agendas à sauvegarder :"));
      var list = app.createListBox(true).setVisibleItemCount(5);
      list.setName('calendar');
      grid.setWidget(0, 1, list);
      var calendars = CalendarApp.getAllCalendars();
      for (var i = 0; i < calendars .length; i++) {
        list.a
        list.addItem(calendars[i].getName());
      }
      grid.setWidget(1, 0, app.createLabel('Date début :'))
          .setWidget(2, 0, app.createDateBox().setId("start").setValue(new Date('2013/09/01')))
          .setWidget(1, 1, app.createLabel('Date fin :'))
          .setWidget(2, 1, app.createDateBox().setId("end").setValue(new Date('2014/07/30')))
      var button = app.createButton('Confirmer');
      var handler = app.createServerClickHandler('bkpToSheet');
      handler.addCallbackElement(grid);
      var cHandler = app.createClientHandler().forTargets(wait).setVisible(true);
      button.addClickHandler(handler).addClickHandler(cHandler);
      grid.setWidget(3, 0,button).setWidget(3, 1, wait);
      app.add(grid);
      doc.show(app);
    }

    function bkpToSheet(e){
      var app = UiApp.getActiveApplication();
      var calNames = e.parameter.calendar.split(',');
      var name = 'BACKUP-AGENDAS-DU-'+Utilities.formatDate(new Date(), FUS1, 'dd-MM-yyyy@HH/mm').replace('/','h')+'-'+calNames.join('&');
      var ss = SpreadsheetApp.create(name)
      ss.setSpreadsheetTimeZone(tz);
      var ssId = ss.getId();
        try{var bkpFolder = DocsList.getFolder('Backup agendas')}
        catch(err){var bkpFolder = DocsList.createFolder('Backup agendas')}
      DocsList.getFileById(ssId).addToFolder(bkpFolder);
      DocsList.getFileById(ssId).removeFromFolder(DocsList.getRootFolder());
      for(n=0;n<calNames.length;++n){
      var sh = ss.insertSheet(calNames[n]);
    //Logger.log(calNames[n])      
          var eventArray = [];
          var startDate = new Date(e.parameter.start);
          var endDate = new Date(e.parameter.end);
          var Calendar = CalendarApp.getCalendarsByName(calNames[n]);
          var events = Calendar[0].getEvents(startDate , endDate);
          if (events[0]){
            var line = new Array();
            for (i = 0; i < events.length; i++) {
              line = new Array();
              var guestList = events[i].getGuestList(false);
              var guests = [];
              var guestsName = []
              for(g=0;g<guestList.length;++g){
                guests.push(guestList[g].getEmail());
                guestsName.push(guestList[g].getName());
              }
              FUS1=events[i].getStartTime().toString().substr(25,6)+":00";
              var title = events[i].getTitle()
              line.push(title);
              line.push(Utilities.formatDate(events[i].getStartTime(), FUS1, 'yyyy/MM/dd HH:mm:ss'));
              line.push(Utilities.formatDate(events[i].getEndTime(), FUS1, 'yyyy/MM/dd HH:mm:ss'));
              line.push(events[i].getLocation());
              line.push(events[i].getCreators());
              line.push(guests.join(','))
              line.push(guestsName.join(','))
              line.push((events[i].getEndTime() - events[i].getStartTime()) / 3600000);
              line.push(events[i].getDescription())
              eventArray.push(line);
            }
          }
           var titre = ["Backup de l'Agenda de "+calNames[n],'Début ','Fin','Lieu/ressources','Créateur','invités (lien)','invités (nom)','durée','description'];
           eventArray.unshift(titre);
           sh.getRange(1,1,eventArray.length,eventArray[0].length).setValues(eventArray);   
      }
      ss.setActiveSheet(ss.getSheets()[0]);
      var delSheet = ss.deleteActiveSheet(); // delete first empty sheet
      var app = UiApp.getActiveApplication();
      app.close();
      return app;  
    }  

    function restore(){
      ScriptProperties.setProperty('restorePointers',[0,0].join('@'))
      var app = UiApp.createApplication().setTitle("Restauration d'agenda à partir des BACKUPS Google Spreadsheets");
      app.setHeight(225).setWidth(780);
      var doc = SpreadsheetApp.getActiveSpreadsheet();
      var waitR = app.createImage('https://dl.dropboxusercontent.com/u/211279/loading3T.gif').setId('waitR').setVisible(false);
      var wait = app.createImage('https://dl.dropboxusercontent.com/u/211279/loading3.gif').setId('wait').setVisible(false);
      var handlerContinueRestore = app.createServerHandler('continueRestore');
      var handlerCancelRestore = app.createServerHandler('cancelRestore');
      var cliHandlerContinue = app.createClientHandler().forEventSource().setEnabled(false).setHTML('Merci, reprise de la restauration').forTargets(waitR).setVisible(true);
      var cont = app.createButton('continuer la restauration',handlerContinueRestore).setId('continue')
      .setStyleAttributes({'fontSize':'12px', padding:'5px', borderRadius:'4px 4px 4px 4px',borderColor:'#ff0000',borderWidth:'2px'}).addClickHandler(cliHandlerContinue); 
      var cancel = app.createButton("Annuler la restauration.", handlerCancelRestore).setId('cancel')
      .setStyleAttributes({'fontSize':'12px', padding:'5px', borderRadius:'4px 4px 4px 4px',borderColor:'#ff0000',borderWidth:'2px'}); 
      var msgHTML = app.createHTML().setId('msgHTML').setStyleAttributes({'fontSize':'12px', 'padding':'25px', 'borderRadius':'4px 4px 4px 4px','borderColor':'#ff0000','borderWidth':'2px'}); 
      var popPanel = app.createVerticalPanel().setPixelSize(587,137).setId('popPanel').setStyleAttributes({background:'#ffffcc',padding:'25px', borderRadius:'12px 12px 12px 12px',borderColor:'#ff0000',borderWidth:'1px'}).setVisible(false); 
      var popGrid = app.createGrid(2,3).setWidth('500');
      popPanel.add(msgHTML).add(popGrid);
      popGrid.setWidget(0,0,cont).setWidget(0,1,cancel).setWidget(0, 2, waitR);
      var cHandler = app.createClientHandler().forTargets(wait).setVisible(true);
      var wHandler = app.createClientHandler(); 
      var grid = app.createGrid(5, 2).setId('grid').addClickHandler(wHandler);
      var dateHandler = app.createServerHandler('restoreDate').addCallbackElement(grid);
      var chkGrid = app.createGrid(1, 7);
          chkGrid.setText(0, 0, 'ressources/invités à restaurer :')
                 .setWidget(0, 1, app.createCheckBox('@salles').setName('salles'))
                 .setWidget(0, 2, app.createCheckBox('#cours').setName('cours'))
                 .setWidget(0, 3, app.createCheckBox('profs').setName('profs'))
                 .setWidget(0, 4, app.createCheckBox('Dates partielles').setName('dates').addClickHandler(dateHandler).addClickHandler(cHandler));
      var list = app.createListBox().setName('bkpname').addItem('- - -');
      var listS = app.createListBox().setName('sourceCal').addItem('- - -');
      var listD = app.createListBox().setName('targetCal').addItem('- - -');
      grid.setText(0, 0, "Backup à utiliser :");
      grid.setWidget(0, 1, list);
      grid.setText(1, 0, "Agenda de la feuille (source) :");
      grid.setWidget(1, 1, listS);
      grid.setText(2, 0, "Agenda cible(destination) :");
      grid.setWidget(2, 1, listD);
      var bkps = DocsList.find('BACKUP-AGENDAS-DU-');
      var bkpList = [];
      for(n=0;n<bkps.length;++n){
    //Logger.log(bkps[n].getName()+' '+bkps[n].getId())
         if(bkps[n].getName().indexOf('BACKUP-AGENDAS-DU-')>-1){
            list.addItem(bkps[n].getName(),bkps[n].getId())
         }
      }
      var calendars = CalendarApp.getAllCalendars();
      for (var i = 0; i < calendars .length; i++) {
        listS.addItem(calendars[i].getName(),calendars[i].getId());
        listD.addItem(calendars[i].getName(),calendars[i].getId());
      }
      var dateDeb = app.createListBox().setId('dateDeb').setName('dateDeb').setVisible(false).addChangeHandler(dateHandler).addItem('première date disponible','xxxxxxxxxx');
      var dateFin = app.createListBox().setId('dateFin').setName('dateFin').setVisible(false).addChangeHandler(dateHandler).addItem('dernière date disponible','xxxxxxxxxx');
      var button = app.createButton('Confirmer').setId('button');
      var msg = app.createButton("cette opération peut être longue... veuillez patienter et attendre la disparition de cette fenêtre<BR>Vous pouvez voir la progression dans le bas de l'écran (date et nombre / total) "
      +"<BR>ou interrompre la restauration en cliquant ici.",handlerCancelRestore).setVisible(false).setId('msg').setStyleAttributes({'fontSize':'12px', padding:'25px',background:'#ffffff',borderWidth:'0px'});
      var handler = app.createServerClickHandler('restoreCal');
      handler.addCallbackElement(grid);
      var cHandler = app.createClientHandler().forEventSource().setVisible(false).forTargets(wait,msg).setVisible(true).forTargets(dateDeb,dateFin).setEnabled(false);
    //  var msgHandler = app.createClientHandler().forTargets(msg).setVisible(true).forTargets(dateDeb,dateFin).setEnabled(false);
      button.addClickHandler(handler).addClickHandler(cHandler).setEnabled(false);
      grid.setWidget(3, 0,button).setWidget(3, 1, chkGrid);
      grid.setWidget(4, 0,dateDeb).setWidget(4, 1, dateFin);
      var warning = app.createLabel("Veuillez d'abord sélectionner un document et les noms des agendas").setId('warning');  
      wHandler.validateNotMatches(list, '- - -').validateNotMatches(listS, '- - -').validateNotMatches(listD, '- - -')
              .forTargets(button).setEnabled(true).forTargets(warning).setVisible(false);
      app.add(grid).add(warning).add(msg).add(popPanel).add(wait);
      cHandler.forTargets(grid).setVisible(false);
      doc.show(app);
    }


    function continueRestore(e){
      var app = UiApp.getActiveApplication();
      var popAlert = app.getElementById('popPanel');
      var grid = app.getElementById('grid');
      var msg = app.getElementById('msg').setVisible(false);
      ScriptProperties.setProperty('startrestore',new Date().getTime().toString());
      // recover pointers to continue restore
      var restoreData = ScriptProperties.getProperty('restoreData');  
      e = Utilities.jsonParse(restoreData);
      return restoreCal(e) 
    }

    function cancelRestore(e){
      var app = UiApp.getActiveApplication();
       ScriptProperties.setProperty('restoreData','')
       ScriptProperties.setProperty('restorePointers','canceled');
       SpreadsheetApp.getActiveSpreadsheet().toast('  ','restauration annulée');
       app.close();
       return app;  
    }  

    function restoreDate(e){
      var app = UiApp.getActiveApplication();
      var wait = app.getElementById('wait');
      var sourceCalId = e.parameter.sourceCal;
      var sourceCal = CalendarApp.getCalendarById(sourceCalId);
      var sourceCalName = sourceCal.getName();
      var ssId = e.parameter.bkpname;
      if(!SpreadsheetApp.openById(ssId).getSheetByName(sourceCalName)){Browser.msgBox('pas de backup correspondant à cet agenda dans ce document');return app}
      var data = SpreadsheetApp.openById(ssId).getSheetByName(sourceCalName).getDataRange().getValues();
      var deb = [];
      var fin = [];
        for(n=1;n<data.length;++n){
          deb.push([Utilities.formatDate(new Date(data[n][1]),tz,'dd-MM-yyyy'),n-1]);
          fin.push([Utilities.formatDate(new Date(data[n][2]),tz,'dd-MM-yyyy'),n-1]);   
        }
    //Logger.log(deb)
      var dateDeb = [];
      var dateFin = [];
       for(n=deb.length-1;n>0;n--){if(deb[n][0]!=deb[n-1][0]){dateDeb.push(deb[n])}};
       for(n=fin.length-1;n>0;n--){if(fin[n][0]!=fin[n-1][0]){dateFin.push(fin[n])}};
      var debList = app.getElementById('dateDeb');
          debList.setVisible(true); 
      var finList = app.getElementById('dateFin');
          finList.setVisible(true); 
      if(e.parameter.dates=='false'){debList.setVisible(false);finList.setVisible(false)}
      dateDeb.push(['première date disponible','xxxxxxxxxx']);
      dateDeb.reverse();
      dateFin.unshift(['dernière date disponible','xxxxxxxxxx']);
      if(e.parameter.dateDeb=='xxxxxxxxxx'){
          debList.clear();
          for(n=0;n<dateDeb.length;++n){debList.addItem(dateDeb[n][0],dateDeb[n][1])}
      }
      if(e.parameter.dateFin=='xxxxxxxxxx'){
          finList.clear();
          for(n=0;n<dateFin.length;++n){finList.addItem(dateFin[n][0],dateFin[n][1])}
      }
      wait.setVisible(false);
      var cHandler = app.createClientHandler().forTargets(wait).setVisible(true);
      app.getElementById('button').addClickHandler(cHandler);
      return app;
    }


    function restoreCal(e){
      var lock = LockService.getPublicLock();
      var success = lock.tryLock(5000);
       if (!success) {
         Logger.log('tryLock failed to get the lock');
         return
       }
      ScriptProperties.setProperty('startrestore',new Date().getTime().toString())
        if(ScriptProperties.getProperty('restoreData')==''||ScriptProperties.getProperties().toString().indexOf('restoreData')==-1)
          {ScriptProperties.setProperty('restoreData',Utilities.jsonStringify(e))
          }
      var app = UiApp.getActiveApplication();
      var alert = app.getElementById('alert')
      var ssId = e.parameter.bkpname;
      var targetCalId = e.parameter.targetCal;
      var targetCal = CalendarApp.getCalendarById(targetCalId);
      var targetCalName = targetCal.getName();
      var sourceCalId = e.parameter.sourceCal;
      var sourceCal = CalendarApp.getCalendarById(sourceCalId);
      var sourceCalName = sourceCal.getName();
      var salles = e.parameter.salles =='true';
      var cours = e.parameter.cours == 'true';
      var profs = e.parameter.profs == 'true';   
      var partiel = e.parameter.dates =='true';
    //Logger.log(SpreadsheetApp.openById(ssId).getNumSheets()+'  '+sourceCalName + salles+cours+profs)
      if(!SpreadsheetApp.openById(ssId).getSheetByName(sourceCalName)){Browser.msgBox('pas de backup correspondant à cet agenda dans ce document');lock.releaseLock(); return app}
      var data = SpreadsheetApp.openById(ssId).getSheetByName(sourceCalName).getDataRange().getValues();
      var headers = data.shift();
    //Logger.log(headers)  
    //Logger.log(ssId+'  '+targetCal)
    //  [Agendas de BaAC1, Début , Fin, Lieu/ressources, Créateur, invités(liens),invités(nom), durée, description]
    // date format = 23/09/2013 12:45:00
       var pointers = ScriptProperties.getProperty('restorePointers');
       if(pointers=='0@0'){
       if(partiel){
    //Logger.log(partiel+' '+e.parameter.dateDeb+' '+e.parameter.dateFin)  
         if(e.parameter.dateDeb.length < 5){var dStart = Number(e.parameter.dateDeb)}else{dStart=0}; 
         if(e.parameter.dateFin.length < 5){var dEnd = Number(e.parameter.dateFin)}else{dEnd=data.length};
       }else{
         var dStart=0;dEnd=data.length;
       }
       }else{
       dStart = Number(pointers.split('@')[0]);
       dEnd = Number(pointers.split('@')[1]);
       }
       if(dStart>dEnd){dStart=dEnd}
    //Logger.log(partiel+' de '+dStart+' à '+dEnd)  
    // main loop ------------------------
        for(var ee=dStart;ee<dEnd;++ee){
         var ccc = ScriptProperties.getProperty('restorePointers');
          if(ccc=='canceled'){ app.close() ; return app };
             if(new Date().getTime()-Number(ScriptProperties.getProperty('startrestore'))>260000){ ;// normal = 260000 mS
               ScriptProperties.setProperty('restorePointers',[ee,dEnd].join('@'));
               var popPanel = app.getElementById('popPanel').setVisible(true);
               var msgHTML = app.getElementById('msgHTML').setHTML("la restauration n'est pas terminée...( encore "+(dEnd-ee)
               +" éléments à restaurer sur "+dEnd+" )<BR> veuillez cliquer ici et le processus reprendra là où il s'était arrêté");
               var msg = app.getElementById('msg').setVisible(false);
               var wait = app.getElementById('wait').setVisible(false);
               var waitR = app.getElementById('waitR').setVisible(false);
               var cont = app.getElementById('continue').setEnabled(true).setHTML('continuer la restauration');
               var grid = app.getElementById('grid').setVisible(false);
               lock.releaseLock();                                                                                   
               return app
               }
          var guests = '';
          var types = data[ee][6].split(',');
          var typesLink = data[ee][5].split(',');
          for(t=0;t<types.length;++t){
            if(types[t].indexOf('@')>-1 && salles){guests+=typesLink[t]+','; continue}
            if(types[t].indexOf('#')>-1 && cours){guests+=typesLink[t]+','; continue}
            if(profs){guests+=typesLink[t]+','}
          }
          if(guests.length>0){guests = guests.substring(0,guests.length-1)}
    //Logger.log(targetCal.getName()+'  '+ee+'  '+ guests)
          var options = {'guests':guests,'location':data[ee][3],'description':data[ee][8]}
          try{
          targetCal.createEvent(data[ee][0], new Date(data[ee][1]), new Date(data[ee][2]), options);
          Utilities.sleep(30);
          }catch(error){
            app.add(app.createLabel('erreur du serveur :'+Utilities.jsonStringify(error)+ ' le processus devrait néanmoins continuer sans intervention de vore part'));
          }
    //Logger.log(ee+'  '+data[ee][0]+'  '+new Date(data[ee][1])+'  '+ new Date(data[ee][2])+'  '+guests)   
       if((ee%10==0)&&ee>0){SpreadsheetApp.getActiveSpreadsheet().toast(ee+' événements crées sur '+dEnd,Utilities.formatDate(new Date(data[ee][1]),tz,'dd-MMM-yyyy'))}
        }
    // end of main loop-----------------  
       ScriptProperties.setProperty('restoreData','')
       ScriptProperties.setProperty('restorePointers',0+'@'+0);
       SpreadsheetApp.getActiveSpreadsheet().toast(dEnd+' événements crées sur '+dEnd,'restauration terminée');
       app.close();
       lock.releaseLock();
       return app;  
    }  
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top