1 /*
2 * Copyright (C) 2007-2008 JVending Masa
3 * Copyright (C) 2011 Jayway AB
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package com.jayway.maven.plugins.android.standalonemojos;
18
19 import com.android.ddmlib.AdbCommandRejectedException;
20 import com.android.ddmlib.FileListingService;
21 import com.android.ddmlib.FileListingService.FileEntry;
22 import com.android.ddmlib.IDevice;
23 import com.android.ddmlib.SyncException;
24 import com.android.ddmlib.SyncService;
25 import com.android.ddmlib.TimeoutException;
26 import com.jayway.maven.plugins.android.AbstractAndroidMojo;
27 import com.jayway.maven.plugins.android.DeviceCallback;
28 import com.jayway.maven.plugins.android.common.DeviceHelper;
29 import com.jayway.maven.plugins.android.common.LogSyncProgressMonitor;
30 import com.jayway.maven.plugins.android.config.ConfigHandler;
31 import com.jayway.maven.plugins.android.config.ConfigPojo;
32 import com.jayway.maven.plugins.android.config.PullParameter;
33 import com.jayway.maven.plugins.android.configuration.Pull;
34 import org.apache.commons.io.FilenameUtils;
35 import org.apache.commons.lang.StringUtils;
36 import org.apache.maven.plugin.MojoExecutionException;
37 import org.apache.maven.plugin.MojoFailureException;
38
39 import java.io.File;
40 import java.io.IOException;
41
42 /**
43 * Copy file or directory from all the attached (or specified)
44 * devices/emulators.
45 *
46 * @author Manfred Moser <manfred@simpligility.com>
47 * @goal pull
48 * @requiresProject false
49 */
50 public class PullMojo extends AbstractAndroidMojo
51 {
52
53 /**
54 * <p>The configuration for the pull goal can be set up in the plugin configuration in the pom file as:</p>
55 * <pre>
56 * <pull>
57 * <source>path</source>
58 * <destination>path</destination>
59 * </pull>
60 * </pre>
61 * <p>The parameters can also be configured as property in the pom or settings file
62 * <pre>
63 * <properties>
64 * <android.pull.source>pathondevice</android.pull.source>
65 * <android.pull.destination>path</android.pull.destination>
66 * </properties>
67 * </pre>
68 * or from command-line with parameter <code>-Dandroid.pull.source=path</code>
69 * and <code>-Dandroid.pull.destination=path</code>.</p>
70 *
71 * @parameter
72 */
73 @ConfigPojo
74 private Pull pull;
75
76 /**
77 * The path of the source file or directory on the emulator/device.
78 *
79 * @parameter expression="${android.pull.source}"
80 */
81 private String pullSource;
82
83 @PullParameter( required = true )
84 private String parsedSource;
85
86 /**
87 * The path of the destination to copy the file to.
88 * <p/>
89 * If destination ends with {@link File#separator}, it is supposed to be a
90 * directory. Therefore the source - whether it refers to a file or
91 * directory - will be copied into the destination directory.
92 * <p/>
93 * If destination does not end with {@link File#separator}, the last path
94 * segment will be assumed as the new file or directory name (depending on
95 * the type of source).
96 * <p/>
97 * Any missing directories will be created.
98 *
99 * @parameter expression="${android.pull.destination}"
100 */
101 private String pullDestination;
102
103 @PullParameter( required = true )
104 private String parsedDestination;
105
106 public void execute() throws MojoExecutionException, MojoFailureException
107 {
108
109 ConfigHandler configHandler = new ConfigHandler( this );
110 configHandler.parseConfiguration();
111
112 doWithDevices( new DeviceCallback()
113 {
114 public void doWithDevice( final IDevice device ) throws MojoExecutionException
115 {
116 String deviceLogLinePrefix = DeviceHelper.getDeviceLogLinePrefix( device );
117
118 // message will be set later according to the processed files
119 String message = "";
120 try
121 {
122 SyncService syncService = device.getSyncService();
123 FileListingService fileListingService = device.getFileListingService();
124
125 FileEntry sourceFileEntry = getFileEntry( parsedSource, fileListingService );
126
127 if ( sourceFileEntry.isDirectory() )
128 {
129 // pulling directory
130 File destinationDir = new File( parsedDestination );
131 if ( ! destinationDir.exists() )
132 {
133 getLog().info( "Creating destination directory " + destinationDir );
134 destinationDir.mkdirs();
135 destinationDir.mkdir();
136 }
137 String destinationDirPath = destinationDir.getAbsolutePath();
138
139 FileEntry[] fileEntries;
140 if ( parsedDestination.endsWith( File.separator ) )
141 {
142 // pull source directory directly
143 fileEntries = new FileEntry[]{ sourceFileEntry };
144 }
145 else
146 {
147 // pull the children of source directory only
148 fileEntries = fileListingService.getChildren( sourceFileEntry, true, null );
149 }
150
151 message = deviceLogLinePrefix + "Pull of " + parsedSource + " to " + destinationDirPath
152 + " from ";
153
154 syncService.pull( fileEntries, destinationDirPath, new LogSyncProgressMonitor( getLog() ) );
155 }
156 else
157 {
158 // pulling file
159 File parentDir = new File( FilenameUtils.getFullPath( parsedDestination ) );
160 if ( ! parentDir.exists() )
161 {
162 getLog().info( deviceLogLinePrefix + "Creating destination directory " + parentDir );
163 parentDir.mkdirs();
164 }
165
166 String destinationFileName;
167 if ( parsedDestination.endsWith( File.separator ) )
168 {
169 // keep original filename
170 destinationFileName = FilenameUtils.getName( parsedSource );
171 }
172 else
173 {
174 // rename filename
175 destinationFileName = FilenameUtils.getName( parsedDestination );
176 }
177
178 File destinationFile = new File( parentDir, destinationFileName );
179 String destinationFilePath = destinationFile.getAbsolutePath();
180 message = deviceLogLinePrefix + "Pull of " + parsedSource + " to " + destinationFilePath
181 + " from " + DeviceHelper.getDescriptiveName( device );
182
183 syncService.pullFile( sourceFileEntry, destinationFilePath,
184 new LogSyncProgressMonitor( getLog() ) );
185 }
186
187 getLog().info( message + " successful." );
188 }
189 catch ( SyncException e )
190 {
191 throw new MojoExecutionException( message + " failed.", e );
192 }
193 catch ( IOException e )
194 {
195 throw new MojoExecutionException( message + " failed.", e );
196 }
197 catch ( TimeoutException e )
198 {
199 throw new MojoExecutionException( message + " failed.", e );
200 }
201 catch ( AdbCommandRejectedException e )
202 {
203 throw new MojoExecutionException( message + " failed.", e );
204 }
205 }
206 } );
207 }
208
209 /**
210 * Retrieves the corresponding {@link FileEntry} on the emulator/device for
211 * a given file path.
212 * <p/>
213 * If the file path starts with the symlink "sdcard", it will be resolved
214 * statically to "/mnt/sdcard".
215 *
216 * @param filePath path to file or directory on device or emulator
217 * @param fileListingService {@link FileListingService} for retrieving the
218 * {@link FileEntry}
219 * @return a {@link FileEntry} object for the given file path
220 * @throws MojoExecutionException if the file path could not be found on the device
221 */
222 private FileEntry getFileEntry( String filePath, FileListingService fileListingService )
223 throws MojoExecutionException
224 {
225 // static resolution of symlink
226 if ( filePath.startsWith( "/sdcard" ) )
227 {
228 filePath = "/mnt" + filePath;
229 }
230
231 String[] destinationPathSegments = StringUtils.split( filePath, "/" );
232
233 FileEntry fileEntry = fileListingService.getRoot();
234 for ( String destinationPathSegment : destinationPathSegments )
235 {
236 // build up file listing cache
237 fileListingService.getChildren( fileEntry, true, null );
238
239 fileEntry = fileEntry.findChild( destinationPathSegment );
240 if ( fileEntry == null )
241 {
242 throw new MojoExecutionException( "Cannot execute goal: " + filePath + " does not exist on device." );
243 }
244 }
245 return fileEntry;
246 }
247 }