@@ -228,6 +228,181 @@ public void uploadFile(java.io.File file, String type) throws IOException {
228228 public void close () {
229229 // nothing needs to be done
230230 }
231+
232+ /**
233+ * fully qualified item id
234+ */
235+ private static class FQID {
236+ public final String driveId ;
237+ public final String itemId ;
238+
239+ public FQID (String driveId , String itemId ) {
240+ this .driveId = driveId ;
241+ this .itemId = itemId ;
242+ }
243+ }
244+
245+ /**
246+ * removes "." and ".." segments from the path
247+ * @param path to normalize
248+ * @return the normalized path
249+ */
250+ @ NotNull
251+ private String normalizePath (String path ) {
252+ StringBuilder normalized = new StringBuilder ();
253+ for (String part : path .split ("[/\\ \\ ]" )) {
254+ if ("." .equals (part ) || ".." .equals (part )) {
255+ continue ;
256+ }
257+ normalized .append ('/' ).append (part );
258+ }
259+ return normalized .substring (1 );
260+ }
261+
262+ /**
263+ * creates all folders in the path if they don't already exist
264+ * @param path to create the folders for
265+ * @return FQID of the last folder in the path
266+ * @throws IOException if the folder could not be created;
267+ * or if the api request could not be executed due to cancellation, a connectivity problem or timeout.
268+ */
269+ @ NotNull
270+ private FQID createPath (String path ) throws IOException {
271+ Iterator <String > parts = Arrays .stream (path .split ("/" )).iterator ();
272+ FQID root = createRootFolder (parts .next ());
273+ for (; parts .hasNext (); ) {
274+ String folder = parts .next ();
275+ root = createFolder (root , folder );
276+ }
277+ return root ;
278+ }
279+
280+ /**
281+ * creates a folder at the root if it doesn't already exist
282+ * @param root of where to create the folder
283+ * @param folder name to create
284+ * @return FQID of the folder
285+ * @throws IOException if the folder could not be created;
286+ * or if the api request could not be executed due to cancellation, a connectivity problem or timeout.
287+ */
288+ @ NotNull
289+ private FQID createFolder (FQID root , String folder ) throws IOException {
290+ FQID item = getFolder (root , folder );
291+ if (item != null ) {
292+ return item ;
293+ }
294+ RequestBody requestBody = RequestBody .create ("{ \" name\" : \" " + folder
295+ + "\" , \" folder\" : {}, \" @microsoft.graph.conflictBehavior\" : \" fail\" }" , jsonMediaType );
296+ Request request = new Request .Builder ()
297+ .addHeader ("Authorization" , "Bearer " + accessToken )
298+ .url ("https://graph.microsoft.com/v1.0/me/drive/root/children" )
299+ .post (requestBody )
300+ .build ();
301+ try (Response response = DriveBackup .httpClient .newCall (request ).execute ()) {
302+ if (!response .isSuccessful ()) {
303+ throw new IOException ("Couldn't create folder " + folder );
304+ }
305+ JSONObject parsedResponse = new JSONObject (response .body ().string ());
306+ String driveId = parsedResponse .getJSONObject ("parentReference" ).getString ("driveId" );
307+ String itemId = parsedResponse .getString ("id" );
308+ return new FQID (driveId , itemId );
309+ }
310+ }
311+
312+ /**
313+ * creates a folder at the drive root if it doesn't already exist
314+ * @param folder name to create
315+ * @return FQID of the folder
316+ * @throws IOException if the folder could not be created;
317+ * or if the api request could not be executed due to cancellation, a connectivity problem or timeout.
318+ */
319+ @ NotNull
320+ private FQID createRootFolder (String folder ) throws IOException {
321+ FQID item = getRootFolder (folder );
322+ if (item != null ) {
323+ return item ;
324+ }
325+ RequestBody requestBody = RequestBody .create ("{ \" name\" : \" "
326+ + folder + "\" , \" folder\" : {}, \" @name.conflictBehavior\" : \" fail\" }" , jsonMediaType );
327+ Request request = new Request .Builder ()
328+ .addHeader ("Authorization" , "Bearer " + accessToken )
329+ .url ("https://graph.microsoft.com/v1.0/me/drive/root/children" )
330+ .post (requestBody )
331+ .build ();
332+ try (Response response = DriveBackup .httpClient .newCall (request ).execute ()) {
333+ if (!response .isSuccessful ()) {
334+ throw new IOException ("Couldn't create folder " + folder );
335+ }
336+ JSONObject parsedResponse = new JSONObject (response .body ().string ());
337+ String driveId = parsedResponse .getJSONObject ("parentReference" ).getString ("driveId" );
338+ String itemId = parsedResponse .getString ("id" );
339+ return new FQID (driveId , itemId );
340+ }
341+ }
342+
343+ /**
344+ * tries to find folder in the drive root
345+ * @param folder to search
346+ * @return FQID or null if not found
347+ */
348+ @ Nullable
349+ private FQID getRootFolder (String folder ) {
350+ try {
351+ Request request = new Request .Builder ()
352+ .addHeader ("Authorization" , "Bearer " + accessToken )
353+ .url ("https://graph.microsoft.com/v1.0/me/drive/root:/" + folder + "?$select=id,parentReference,remoteItem" )
354+ .build ();
355+ JSONObject parsedResponse ;
356+ try (Response response = DriveBackup .httpClient .newCall (request ).execute ()) {
357+ parsedResponse = new JSONObject (response .body ().string ());
358+ }
359+ if (parsedResponse .has ("remoteItem" )) {
360+ parsedResponse = parsedResponse .optJSONObject ("remoteItem" );
361+ }
362+ String driveId = parsedResponse .getJSONObject ("parentReference" ).getString ("driveId" );
363+ String itemId = parsedResponse .getString ("id" );
364+ return new FQID (driveId , itemId );
365+ } catch (Exception exception ) {
366+ return null ;
367+ }
368+ }
369+
370+ /**
371+ * tries to find a folder under root
372+ * @param root to search
373+ * @param folder to look for
374+ * @return FQID or null if not found
375+ */
376+ @ Nullable
377+ private FQID getFolder (FQID root , String folder ) {
378+ try {
379+ Request request = new Request .Builder ()
380+ .addHeader ("Authorization" , "Bearer " + accessToken )
381+ .url ("https://graph.microsoft.com/v1.0/me/drives/" + root .driveId + "/items/" + root .itemId + "/children" )
382+ .build ();
383+ JSONObject parsedResponse ;
384+ try (Response response = DriveBackup .httpClient .newCall (request ).execute ()) {
385+ parsedResponse = new JSONObject (response .body ().string ());
386+ }
387+ JSONArray children = parsedResponse .getJSONArray ("value" );
388+ for (int i = 0 ; i < children .length (); i ++) {
389+ JSONObject childItem = children .getJSONObject (i );
390+ String folderName = childItem .getString ("name" );
391+ if (folder .equals (folderName )) {
392+ if (childItem .has ("remoteItem" )) {
393+ childItem = childItem .optJSONObject ("remoteItem" );
394+ }
395+ String driveId = childItem .getJSONObject ("parentReference" ).getString ("driveId" );
396+ String itemId = childItem .getString ("id" );
397+ return new FQID (driveId , itemId );
398+ }
399+ }
400+ // TODO handle @odata.nextLink
401+ } catch (Exception exception ) {
402+ return null ;
403+ }
404+ return null ;
405+ }
231406
232407 /**
233408 * Creates a folder with the specified name in the specified parent folder in the authenticated user's OneDrive.
0 commit comments