# Verzeichnisse mit Inhalt nach SMB-Share kopieren



## lal000r (4. Feb 2013)

Hallo zusammen,

ich hänge seit längerer Zeit an einer bestimmten Stelle bei der Implementierung eines Kopierprogramms fest.
Zur Einbindung von SMB-Shares greife ich auf die Jcifs Bibliothek zurück - hier ist auch mein Problem:

Ich bekomme es nicht hin, die zu kopierenden Verzeichnisse mit SmbFile.mkdirs() zu erstellen. Laut API-Doc soll das auch nicht funktionieren - wenn die SMB-Url aber in einem bestimmten Format vorliegt, soll es funktionieren.

Zunächst mach ich also Folgendes:
File-Objekte instanziieren und an die Methode copyDir() übergeben:

```
LocalFile file = new LocalFile("C:/Neuer Ordner");
SmbFile smbfile = new SmbFile("smb://192.168.178.250/Daten/ISOs/",new NtlmPasswordAuthentication("", "user", "pass"));

try {
		FileOperation.copyDir(file, smbfile);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
```

Diese Methode kopiert die Verzeichnisse rekursiv, scheitert aber direkt am dest.mkdir(). :

```
public static void copyDir(File src, SmbFile dest)
			throws FileNotFoundException, IOException {
		if (src.isDirectory()) {
			File[] files = src.listFiles();
			if (files != null) {
				for (File f : files) {
					dest.mkdir(); // Erstelle gegebenfalls neuen Unterordner am Zielort.
					copyDir(f, new SmbFile(dest.getPath()+ f.getName())); // Kopiere alles in diesem Unterordner
				}
			}
		}
		else {
			copyFile(src, dest);
		}
	}
```

Und zu guter Letzt noch die CopyFile-Methode:

```
public static void copyFile(File src, SmbFile dest) throws FileNotFoundException, IOException {
        SmbFileOutputStream fileout = null;
        FileInputStream filein = null;
        new File(dest.getPath().replaceAll(dest.getName(), "")).mkdirs();
        System.out.println(dest.getPath().replaceAll(dest.getName(), ""));
        try {
            filein = new FileInputStream(src);
            fileout = new SmbFileOutputStream(dest);
            int count;
            byte[] data = new byte[8192];
            while ((count = filein.read(data)) != -1) {
                fileout.write(data, 0, count);
            }
        } finally {
            safeClose(fileout);
            safeClose(filein);
        }
    }
```

Das Problem ist also, dass ich kein Verzeichnis im SMB-Share erstellen kann und folgende Exception bekomme:

jcifs.smb.SmbException: Cannot create a file when that file already exists.
at jcifs.smb.SmbTransport.checkStatus(SmbTransport.java:563)

Die zu spiegelnden Ordner sind natürlich im SMB-Share noch nicht vorhanden (auch wenn die Exception das vermuten lässt).
Hat jemand Erfahrung mit Jcifs und kann mir sagen, wie es am besten möglich ist, Verzeichnisse in ein SMB-Share zu spiegeln? 
Vielen Dank schonmal!


----------



## maki (4. Feb 2013)

```
jcifs.smb.SmbException: Cannot create a file when that file already exists.
at jcifs.smb.SmbTransport.checkStatus(SmbTransport.java:563)
```
Was steht denn in dieser Zeile?

Ins Blaue geraten:
Solltest wohl besser prüfen ob der Ordner nicht schon erstellt wurde (zB. durch einen vorherigen Aufruf der copyDir Methode) bevor du mkdirs aufrufst.


----------



## lal000r (4. Feb 2013)

maki hat gesagt.:


> ```
> jcifs.smb.SmbException: Cannot create a file when that file already exists.
> at jcifs.smb.SmbTransport.checkStatus(SmbTransport.java:563)
> ```
> ...



Die angegebene Zeile in der Exception steht irgendwo in der SmbTransport-Klasse, die ich jedoch nicht geschrieben habe, sondern lediglich mit der Jcifs.jar benutze. Bei mir im Code tritt es direkt beim ersten dest.mkdir() auf.

Später werde ich sicherlich vorher noch prüfen, ob diese Ordner bereits existieren, aber derzeit ist der Zielordner leer. (Alles nur zum Testen)
Vielleicht sollte ich noch erwähnen, dass diese Methoden bei normalen File-Objekten einwandfrei funktionieren.
Es hat eindeutig was mit dem SMB-Share und vielleicht dessen URL zu tun.


----------



## maki (4. Feb 2013)

> Vielleicht sollte ich noch erwähnen, dass diese Methoden bei normalen File-Objekten einwandfrei funktionieren.


Meinst du damit java.io.File?

Wenn ja, dann ist das normal, mkdir gibt da einen boolean zurück und wirft keine Exception:
File (Java Platform SE 7 )


----------



## lal000r (4. Feb 2013)

maki hat gesagt.:


> Meinst du damit java.io.File?
> 
> Wenn ja, dann ist das normal, mkdir gibt da einen boolean zurück und wirft keine Exception:
> File (Java Platform SE 7 )



Ja, meine ich.  Mit klassischen File-Objekten habe ich auch keine Probleme, mir geht's nur um SmbFiles.
Es wirft keine Exception und die Verzeichnisse werden inkl. Inhalt auch kopiert - nur eben auf lokale Pfade.


----------



## nillehammer (4. Feb 2013)

Kenne mich mit dem SMBFile-Krams jetzt nicht so aus, aber das hier sieht merkwürdig aus (siehe mein Kommentar im Code):

```
public static void copyDir(File src, SmbFile dest)
            throws FileNotFoundException, IOException {
        if (src.isDirectory()) {
            File[] files = src.listFiles();
            if (files != null) {
                for (File f : files) {
                    // Hier legst Du wiederholt denselben Ordner auf dem Ziel an, denn "dest" ändert sich während
                    // des Schleifendurchlaufs nicht. Das gehört meiner Meinung nach aus der for-Schleife raus,
                    // direkt vor "File[] files = src.listFiles();"
                    dest.mkdir(); // Erstelle gegebenfalls neuen Unterordner am Zielort.
                    
                    copyDir(f, new SmbFile(dest.getPath()+ f.getName())); // Kopiere alles in diesem Unterordner
                }
        }
        else {
            copyFile(src, dest);
        }
    }
```


----------



## lal000r (4. Feb 2013)

Ja danke, da hast du recht. Das hat wahrscheinlich den Effekt, dass in dem Moment die gleichnamigen bereits erstellten Ordner wieder überschrieben werden, richtig? Denn hier kommt auf lokalen Verzeichnissen keine Exception, wenn ich das in der Schleife lasse.
Den eigentlichen Fehler behebt das allerdings nicht. Das Erstellen der Ordner auf einem SMB-Share funktioniert weiterhin nicht.


----------



## nillehammer (4. Feb 2013)

> Ja danke, da hast du recht. Das hat wahrscheinlich den Effekt, dass in dem Moment die gleichnamigen bereits erstellten Ordner wieder überschrieben werden, richtig?


Wie gesagt, kenne die SMB-Lib nicht. Aber ich denke, das ist nicht richtig. Ich glaube eher, dass es beim ersten Durchlauf noch funktioniert und beim zweiten dann die von Dir in der Ursprungsfrage erwähnte Exception fliegt.


> Denn hier kommt auf lokalen Verzeichnissen keine Exception,


Dazu hat maki schon die Antwort geschrieben. Vielleicht nicht ausführlich genug. Nur, weil ein SMBFile sich wegen der gleichnamigen Methoden so "anfühlt", wie ein normales File, heißt das nicht, dass sich die Methoden absolut identisch verhalten müssen. Die File-mkdir ist eben so implementiert, dass sie durch returnen von false signalisiert, dass das Dir nicht erzeugt werden konnte. Währen SMBFile das mit einer Exception macht. Du kannst nicht erwarten, dass Code,der die File-API aufruft, sich genauso verhält, wie Code, der letztendlich ja über Netzwerk arbeitet.


----------



## lal000r (4. Feb 2013)

nillehammer hat gesagt.:


> Wie gesagt, kenne die SMB-Lib nicht. Aber ich denke, das ist nicht richtig. Ich glaube eher, dass es beim ersten Durchlauf noch funktioniert und beim zweiten dann die von Dir in der Ursprungsfrage erwähnte Exception fliegt.



Also nach Debugging und Einbau von Testausgaben erweckt es eher den Eindruck, als wenn die Exception direkt beim ersten Aufruf geworfen wird. Eine dahinter liegende Ausgabe wird dann gar nicht erst ausgeführt.



nillehammer hat gesagt.:


> Dazu hat maki schon die Antwort geschrieben. Vielleicht nicht ausführlich genug. Nur, weil ein SMBFile sich wegen der gleichnamigen Methoden so "anfühlt", wie ein normales File, heißt das nicht, dass sich die Methoden absolut identisch verhalten müssen. Die File-mkdir ist eben so implementiert, dass sie durch returnen von false signalisiert, dass das Dir nicht erzeugt werden konnte. Währen SMBFile das mit einer Exception macht. Du kannst nicht erwarten, dass Code,der die File-API aufruft, sich genauso verhält, wie Code, der letztendlich ja über Netzwerk arbeitet.



Das ist mir bewusst. Wenn also die Ordnerstruktur in letzter Konsequenz korrekt erzeugt wurde, wurde vielleicht zwischendurch einige Male "false" zurückgegeben anstatt einer Exception. Das meintest du, oder?

In der Zwischenzeit habe ich noch folgende Aussagen gefunden (meine URL liegt aber eigentlich im letztgenannten Format vor):

```
(new SmbFile("smb://server/share/")).mkdir()  // not possible
(new SmbFile("smb://server/")).mkdir()  // not possible
(new SmbFile("smb://workgroup/")).mkdir()  // not possible
(new SmbFile("smb://server/share/directory/")).mkdir()  // ok if you have permission
```


----------



## nillehammer (4. Feb 2013)

> Also nach Debugging und Einbau von Testausgaben erweckt es eher den Eindruck, als wenn die Exception direkt beim ersten Aufruf geworfen wird. Eine dahinter liegende Ausgabe wird dann gar nicht erst ausgeführt.


Es ist immer gut, Debugging und Testausgaben zu benutzen, aber da wir ja beide schon festgestellt haben, dass die Verzeichniserzeugung aus der Schleife raus gehört, ist es vielleicht eine gute Idee, das mal zu machen und dann den neuen Code zu debuggen.


> Wenn also die Ordnerstruktur in letzter Konsequenz korrekt erzeugt wurde, wurde vielleicht zwischendurch einige Male "false" zurückgegeben anstatt einer Exception. Das meintest du, oder?


Ja, genau das meinte ich. Und, weil Du dieses "false" wahrscheinlich nirgends speicherst und auswertest, ist es Dir wahrscheinlich nicht aufgefallen. Das ist übrigens auch der Grund, warum ICH das returnen spezieller Werte zur Anzeige von Fehlern fragwürdig finde. Eine Exception ist in Java nunmal das eindeutige Mittel, um einen Fehler anzuzeigen. Aber es gibt ja auch Exception-schmeißende Methoden in der File-API.... ich schweife gerade ab...


----------



## lal000r (4. Feb 2013)

nillehammer hat gesagt.:


> Es ist immer gut, Debugging und Testausgaben zu benutzen, aber da wir ja beide schon festgestellt haben, dass die Verzeichniserzeugung aus der Schleife raus gehört, ist es vielleicht eine gute Idee, das mal zu machen und dann den neuen Code zu debuggen.



Das bezog sich schon auf den angepassten Code.


----------



## tröööt (4. Feb 2013)

@TO
lesen ... und das gelesene verstehen ist nicht deine stärke oder ?



> Was steht denn in dieser Zeile?



mit dieser frage war nicht gemeint wo die exception auftritt ... sondern was deren aussage ist :

*jcifs.smb.SmbException: Cannot create a file when that file already exists*

oder auf deutsch : die exception wird geworfen weil eine datei / ein ordner an einer bestimmten stelle nicht neu erstellt werden kann *WEIL DIESER BEREITS EXISTIERT*


----------



## nillehammer (4. Feb 2013)

Ok, dann nächste Baustelle Du übergibst dieses SMBFile an Deine copyDir-Methode:

```
SmbFile smbfile = new SmbFile("smb://192.168.178.250/Daten/ISOs/",new NtlmPasswordAuthentication("", "user", "pass"));
 
try {
        FileOperation.copyDir(file, smbfile);
```
Da dieser Pfad so hart verdrahtet ist, nehme ich an, dass das ein Share ist, den es genauso schon gibt. Genau auf diesem Objekt rufst Du dann mkdir() auf. Du willst doch eigentlich erst die *Unter*ordner des o.g. Pfades erzeugen oder?


----------



## nillehammer (4. Feb 2013)

[OT]Können Männer eigentlich auch "Ihre Tage haben"?[/OT]


----------



## lal000r (4. Feb 2013)

tröööt hat gesagt.:


> @TO
> lesen ... und das gelesene verstehen ist nicht deine stärke oder ?
> 
> 
> ...




Jaa okay, ganz locker bleiben. Ich habe schon verstanden, dass laut Exception der Pfad schon existiert. Nur tut er es eben nicht, es sei denn, die Methode arbeitet mit falschen Parametern... und da kommt das ins Spiel. Autsch.



nillehammer hat gesagt.:


> Ok, dann nächste Baustelle Du übergibst dieses SMBFile an Deine copyDir-Methode:
> 
> ```
> SmbFile smbfile = new SmbFile("smb://192.168.178.250/Daten/ISOs/",new NtlmPasswordAuthentication("", "user", "pass"));
> ...



Richtig... der Share ist: "smb://192.168.178.250/Daten/" und ein bereits vorhandener Ordner im Share ist "ISOs". :shock: Das würde auch erklären, warum direkt das erste mkdir() fehlschlägt. Trotzdem stehe ich auf dem Schlauch, wie ich hier direkt das zu erstellende Unterverzeichnis mitgebe.


----------



## nillehammer (5. Feb 2013)

> Trotzdem stehe ich auf dem Schlauch, wie ich hier direkt das zu erstellende Unterverzeichnis mitgebe.




```
static final String USER = "user";
static final String PASS = "pass";
static final NtlmPasswordAuthentication AUTH = new NtlmPasswordAuthentication("",USER , PASS);
static final String BASE_PATH = "smb://192.168.178.250/Daten/ISOs/";


... dann in irgend einer Methode...
final String subPath = ermittleSubPath(); // wie auch immer Du den SubPath ermittelst, 
                                                    // kann auch ein Methodenparameter sein
final String newPath = BASE_PATH + subPath;
final SmbFile smbfile = new SmbFile(newPath,AUTH);

if(!smbfile.exists()) {
  smbfile.mkdir();
}
...
```
Du wunderst Dich vielleicht, warum Du jetzt relativ oft neue SmbFiles erzeugst. Aber das sind ja auch genau die, die es auf dem Server noch nicht gibt. Je neuem File auf dem Server ein new SmbFile. Darauf wird es glaube ich hinauslaufen. 

P.S. Ach ja, und ein Blick in die API-Dokumentation lohnt wirklich. Ist auch recht übersichtlich, weil es nicht sehr viele Klassen sind: JCIFS API


----------



## lal000r (5. Feb 2013)

nillehammer hat gesagt.:


> ```
> static final String USER = "user";
> static final String PASS = "pass";
> static final NtlmPasswordAuthentication AUTH = new NtlmPasswordAuthentication("",USER , PASS);
> ...




Danke für deine Anregungen. Wenn ich die Methode copyDir() erstmal nicht verändern würde, würde es meiner Meinung nach schon folgender Aufruf tun, um beim ersten Ausführen ein passendes dest-SmbFile zu übergeben. In diesem Fall habe ich einfach den Namen des aktuellen File-Objekts angehangen.

```
SmbFile smbfile = new SmbFile("smb://192.168.178.250/Daten/ISOs/"+file.getName(),new NtlmPasswordAuthentication("", "user", "pass"));
```


----------

