/* * Catroid: An on-device visual programming system for Android devices * Copyright (C) 2010-2016 The Catrobat Team * (<http://developer.catrobat.org/credits>) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * An additional term exception under section 7 of the GNU Affero * General Public License, version 3, is available at * http://developer.catrobat.org/license_additional_term * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.catrobat.catroid.merge; import android.content.Context; import android.util.Log; import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.R; import org.catrobat.catroid.common.Constants; import org.catrobat.catroid.common.LookData; import org.catrobat.catroid.common.ProjectData; import org.catrobat.catroid.common.SoundInfo; import org.catrobat.catroid.content.Project; import org.catrobat.catroid.content.Scene; import org.catrobat.catroid.content.Script; import org.catrobat.catroid.content.Sprite; import org.catrobat.catroid.content.XmlHeader; import org.catrobat.catroid.content.bricks.Brick; import org.catrobat.catroid.io.StorageHandler; import org.catrobat.catroid.stage.StageListener; import org.catrobat.catroid.ui.adapter.ProjectListAdapter; import org.catrobat.catroid.utils.ToastUtil; import org.catrobat.catroid.utils.UtilFile; import org.catrobat.catroid.utils.Utils; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class MergeTask { private Context context; private Scene mergeResult = null; private Scene firstScene = null; private Scene secondScene = null; private Scene current = null; private Project firstProject = null; private Project secondProject = null; private Project mergedProject = null; private ProjectListAdapter adapter = null; private boolean addScene = false; public MergeTask(Project firstProject, Project secondProject, Context context, ProjectListAdapter adapter, boolean addScene) { if (addScene) { this.firstProject = firstProject.getProjectLists().size() == 1 ? secondProject : firstProject; this.secondProject = firstProject.getProjectLists().size() == 1 ? firstProject : secondProject; } else { this.firstProject = firstProject; this.secondProject = secondProject; } this.context = context; this.adapter = adapter; this.addScene = addScene; } public MergeTask(Scene firstScene, Scene secondScene, Context context) { this.firstScene = firstScene; this.secondScene = secondScene; this.context = context; } public boolean mergeProjects(String mergeProjectName) { ArrayList<String> handledScenes = new ArrayList<>(); mergedProject = new Project(context, mergeProjectName); mergedProject.removeScene(mergedProject.getDefaultScene()); createHeader(); Scene defaultScene = secondProject.getDefaultScene(); String sceneName = defaultScene.getName(); if (firstProject.getSceneList().size() == 1 && secondProject.getSceneList().size() == 1) { sceneName = firstProject.getDefaultScene().getName(); } else if (addScene) { sceneName = Utils.getUniqueSceneName(defaultScene.getName(), firstProject, secondProject); } if (!defaultScene.rename(sceneName, context, false)) { handleError(); return false; } for (Scene firstScene : firstProject.getSceneList()) { for (Scene secondScene : secondProject.getSceneList()) { if (firstScene.getName().equals(secondScene.getName())) { this.firstScene = firstScene; this.secondScene = secondScene; if (!mergeScenes(firstScene.getName())) { handleError(); return false; } handledScenes.add(firstScene.getName()); handledScenes.add(secondScene.getName()); break; } } } for (Scene scene : firstProject.getSceneList()) { if (!handledScenes.contains(scene.getName())) { addSceneToProject(scene, firstProject, mergedProject); } } for (Scene scene : secondProject.getSceneList()) { if (!handledScenes.contains(scene.getName())) { addSceneToProject(scene, secondProject, mergedProject); } } try { StorageHandler.getInstance().saveProject(mergedProject); ProjectManager.getInstance().loadProject(mergedProject.getName(), context); } catch (Exception e) { Log.e("MergeTask", "Can't save the merged project"); handleError(); return false; } File projectCodeFile = new File(Utils.buildPath(Utils.buildProjectPath(mergeProjectName), Constants.PROJECTCODE_NAME)); if (adapter != null) { adapter.insert(new ProjectData(mergeProjectName, projectCodeFile.lastModified()), 0); adapter.notifyDataSetChanged(); } String msg = firstProject.getName() + " " + context.getString(R.string.merge_info) + " " + secondProject.getName() + "!"; ToastUtil.showSuccess(context, msg); return true; } public boolean mergeScenesInCurrentProject(String sceneName) { mergedProject = ProjectManager.getInstance().getCurrentProject(); if (!mergeScenes(sceneName)) { StorageHandler.getInstance().deleteScene(mergedProject.getName(), sceneName); return false; } else { StorageHandler.getInstance().saveProject(mergedProject); StorageHandler.getInstance().loadProject(mergedProject.getName(), context); return true; } } public boolean mergeScenes(String sceneName) { mergeResult = new Scene(context, sceneName, mergedProject); if (mergeResult.getSpriteList().size() > 0) { mergeResult.getSpriteList().remove(0); } createSpritesAllScenes(); copySpriteScriptsAllScenes(); if (!ConflictHelper.checkMergeConflict(context, mergeResult)) { return false; } mergedProject.addScene(mergeResult); try { StorageHandler.getInstance().copyImageFiles(mergeResult.getName(), mergeResult.getProject().getName(), firstScene.getName(), firstScene.getProject().getName()); StorageHandler.getInstance().copySoundFiles(mergeResult.getName(), mergeResult.getProject().getName(), firstScene.getName(), firstScene.getProject().getName()); StorageHandler.getInstance().copyImageFiles(mergeResult.getName(), mergeResult.getProject().getName(), secondScene.getName(), secondScene.getProject().getName()); StorageHandler.getInstance().copySoundFiles(mergeResult.getName(), mergeResult.getProject().getName(), secondScene.getName(), secondScene.getProject().getName()); } catch (Exception e) { Log.e("MergeTask", "Something went wrong while copying looks and sounds!"); return false; } File automaticScreenshotDestination = new File(Utils.buildScenePath(mergedProject.getName(), mergeResult.getName()), StageListener.SCREENSHOT_AUTOMATIC_FILE_NAME); File automaticScreenshotSource = new File(Utils.buildScenePath(firstScene.getProject().getName(), firstScene.getName()), StageListener.SCREENSHOT_AUTOMATIC_FILE_NAME); File manualScreenshotDestination = new File(Utils.buildScenePath(mergedProject.getName(), mergeResult.getName()), StageListener.SCREENSHOT_MANUAL_FILE_NAME); File manualScreenshotSource = new File(Utils.buildScenePath(firstScene.getProject().getName(), firstScene.getName()), StageListener.SCREENSHOT_MANUAL_FILE_NAME); try { if (automaticScreenshotSource.exists()) { UtilFile.copyFile(automaticScreenshotDestination, automaticScreenshotSource); } if (manualScreenshotSource.exists()) { UtilFile.copyFile(manualScreenshotDestination, manualScreenshotSource); } } catch (IOException e) { Log.e("MergeTask", "Something went wrong while copying Screenshot Files!"); Log.e("MergeTask", "Copy " + automaticScreenshotSource.getAbsolutePath() + " to " + automaticScreenshotDestination.getAbsolutePath()); Log.e("MergeTask", "Copy " + manualScreenshotSource.getAbsolutePath() + " to " + manualScreenshotDestination.getAbsolutePath()); return false; } return true; } private void handleError() { StorageHandler.getInstance().deleteProject(mergedProject.getName()); try { ProjectManager.getInstance().loadProject(firstProject.getName(), context); ProjectManager.getInstance().setCurrentProject(firstProject); } catch (Exception e) { Log.e("MergeTask", "Error while clean DataStore from unnecessary files"); } } private boolean addSceneToProject(Scene toAdd, Project oldProject, Project project) { toAdd.setProject(project); toAdd.getDataContainer().setProject(project); project.addScene(toAdd); try { StorageHandler.getInstance().copyImageFiles(toAdd.getName(), project.getName(), toAdd.getName(), oldProject.getName()); StorageHandler.getInstance().copySoundFiles(toAdd.getName(), project.getName(), toAdd.getName(), oldProject.getName()); } catch (Exception e) { Log.e("MergeTask", "Something went wrong while copying Screenshot Files!"); return false; } File automaticScreenshotDestination = new File(Utils.buildScenePath(project.getName(), toAdd.getName()), StageListener.SCREENSHOT_AUTOMATIC_FILE_NAME); File automaticScreenshotSource = new File(Utils.buildScenePath(oldProject.getName(), toAdd.getName()), StageListener.SCREENSHOT_AUTOMATIC_FILE_NAME); File manualScreenshotDestination = new File(Utils.buildScenePath(project.getName(), toAdd.getName()), StageListener.SCREENSHOT_MANUAL_FILE_NAME); File manualScreenshotSource = new File(Utils.buildScenePath(oldProject.getName(), toAdd.getName()), StageListener.SCREENSHOT_MANUAL_FILE_NAME); toAdd.setProject(oldProject); toAdd.getDataContainer().setProject(oldProject); try { if (automaticScreenshotSource.exists()) { UtilFile.copyFile(automaticScreenshotDestination, automaticScreenshotSource); } if (manualScreenshotSource.exists()) { UtilFile.copyFile(manualScreenshotDestination, manualScreenshotSource); } } catch (IOException e) { Log.e("MergeTask", "Something went wrong while copying Screenshot Files!"); Log.e("MergeTask", "Copy " + automaticScreenshotSource.getAbsolutePath() + " to " + automaticScreenshotDestination.getAbsolutePath()); Log.e("MergeTask", "Copy " + manualScreenshotSource.getAbsolutePath() + " to " + manualScreenshotDestination.getAbsolutePath()); return false; } return true; } private void createHeader() { XmlHeader mainHeader; XmlHeader subHeader; XmlHeader mergeHeader = mergedProject.getXmlHeader(); int firstProjectSpriteCount = 0; int secondProjectSpriteCount = 0; for (Scene scene : firstProject.getSceneList()) { firstProjectSpriteCount += scene.getSpriteList().size(); } for (Scene scene : secondProject.getSceneList()) { secondProjectSpriteCount += scene.getSpriteList().size(); } if (firstProjectSpriteCount < 2 && secondProjectSpriteCount > 1) { mainHeader = secondProject.getXmlHeader(); subHeader = firstProject.getXmlHeader(); } else { mainHeader = firstProject.getXmlHeader(); subHeader = secondProject.getXmlHeader(); } mergeHeader.setVirtualScreenWidth(mainHeader.virtualScreenWidth); mergeHeader.setVirtualScreenHeight(mainHeader.virtualScreenHeight); mergeHeader.setRemixParentsUrlString(Utils.generateRemixUrlsStringForMergedProgram(mainHeader, subHeader)); mergedProject.setXmlHeader(mergeHeader); } private void createSpritesAllScenes() { current = firstScene; createSprites(firstScene); current = secondScene; createSprites(secondScene); } private void createSprites(Scene scene) { for (Sprite sprite : scene.getSpriteList()) { if (!mergeResult.containsSpriteBySpriteName(sprite.getName())) { mergeResult.addSprite(sprite); } } } private void copySpriteScriptsAllScenes() { current = firstScene; copySpriteScripts(firstScene); current = secondScene; copySpriteScripts(secondScene); mergeResult.removeUnusedBroadcastMessages(); } private void copySpriteScripts(Scene scene) { ReferenceHelper helper = new ReferenceHelper(mergeResult, scene); for (Sprite sprite : scene.getSpriteList()) { checkScripts(sprite); addSoundsAndLooks(sprite); } mergeResult = helper.updateAllReferences(); } private void checkScripts(Sprite sprite) { Sprite spriteInto = mergeResult.getSpriteBySpriteName(sprite.getName()); for (Script fromScript : sprite.getScriptList()) { boolean equal = false; for (Script intoScript : spriteInto.getScriptList()) { equal |= isEqualScript(intoScript, fromScript); } if (spriteInto.getScriptList().size() == 0 || !equal) { mergeResult.getSpriteBySpriteName(sprite.getName()).addScript(fromScript); } } } private void addSoundsAndLooks(Sprite sprite) { Sprite spriteInto = mergeResult.getSpriteBySpriteName(sprite.getName()); for (LookData look : sprite.getLookDataList()) { if (!spriteInto.existLookDataByName(look) && !spriteInto.existLookDataByFileName(look)) { spriteInto.getLookDataList().add(look.clone()); } else if (spriteInto.existLookDataByName(look) && !spriteInto.existLookDataByFileName(look)) { for (int i = 2; i < 100; i++) { look.setLookName(look.getLookName() + "_" + i); if (!spriteInto.existLookDataByName(look)) { break; } } spriteInto.getLookDataList().add(look.clone()); } } for (SoundInfo sound : sprite.getSoundList()) { if (!spriteInto.existSoundInfoByName(sound) || !spriteInto.existSoundInfoByFileName(sound)) { spriteInto.getSoundList().add(sound.clone()); } } } private boolean isEqualScript(Script intoProjectScript, Script fromProjectScript) { List<Brick> intoProjectBrickList = intoProjectScript.getBrickList(); List<Brick> fromProjectBrickList = fromProjectScript.getBrickList(); int counter = 0; int numberOfEqualBricks = (intoProjectBrickList.size() < fromProjectBrickList.size()) ? intoProjectBrickList.size() : fromProjectBrickList.size(); for (int i = 0; i < numberOfEqualBricks; i++) { Brick intoProjectBrick = intoProjectBrickList.get(i); Brick fromProjectBrick = fromProjectBrickList.get(i); if (intoProjectBrick.isEqualBrick(fromProjectBrick, mergeResult, current)) { counter++; } } if (counter != numberOfEqualBricks) { return false; } return true; } }